New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pseudo-Introspection, or standard interface detection #165

Closed
chriseth opened this Issue Oct 28, 2016 · 57 comments

Comments

Projects
None yet
@chriseth
Contributor

chriseth commented Oct 28, 2016

This EIP is being further developed as #881


For some "standard interfaces" like the token interface ( #20 ), it is sometimes useful to query whether a contract supports the interface and if yes, which version of the interface, in order to adapt the way in which the contract is interfaced with. Specifically for #20, a version identifier has already been proposed. This proposal wants to generalize the concept of interfaces and interface versions to interface identifiers:

Every "standard interface" should be assigned a unique identifier (the sha3 hash of its source representation would be suitable) as a bytes32 value. Interfaces that support pseudo-introspection should provide the following methods. Note that this is especially targeted at contracts that can also provide multiple non-conflicting interfaces. This is useful if e.g. a standard token contract also allows a "cheque" functionality, which would have its own interface ID, or if the token does not support allowances.

/// @returns true iff the interface is supported
function supportsInterface(bytes32 interfaceID) constant returns (bool);
/// @returns the (main) interface ID of this contract
function interfaceID() constant returns (bytes32);
/// @returns a bit mask of the supported interfaces.
function supportsInterfaces(bytes32[] interfaceIDs) constant returns (bytes32 r) {
  if (interfaceIDs.length > 256) throw;
  for (uint i = 0; i < interfaceIDs.length; i++)
    if (supportsInterface(interfaceIDs[i]))
      r |= bytes32(2**i);
}

Example implementation and usage:

contract GenericInterfaceContract {
    function interfaceID() constant returns (bytes32);
    function supportsInterface(bytes32 _interfaceID) constant returns (bool);
    function supportsInterfaces(bytes32[] interfaceIDs) constant returns (bytes32 r) {
      if (interfaceIDs.length > 256) throw;
      for (uint i = 0; i < interfaceIDs.length; i++)
        if (supportsInterface(interfaceIDs[i]))
          r |= bytes32(2**i);
    }
}
contract SimpleToken is GenericInterfaceContract {
    bytes32 public constant interfaceID = 0xf2a53462c853462ca86b71b97dd84af6a2f7689fc12ea917d9029117d32b9fde;
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    function totalSupply() constant returns (uint256 supply);
    function balanceOf(address _owner) constant returns (uint256 balance);
    function transfer(address _to, uint256 _value) returns (bool success);

    function supportsInterface(bytes32 _interfaceID) constant returns (bool) {
        return _interfaceID == interfaceID;
    }
}
contract Token is SimpleToken {
    bytes32 public constant interfaceID = 0xa2f7689fc12ea917d9029117d32b9fdef2a53462c853462ca86b71b97dd84af6;
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
    function approve(address _spender, uint256 _value) returns (bool success);
    function allowance(address _owner, address _spender) constant returns (uint256 remaining);

    function supportsInterface(bytes32 _interfaceID) constant returns (bool) {
        return _interfaceID == interfaceID || super.supportsInterface(_interfaceID);
    }
}
contract C {
    function retrieveRemainingAllowance(GenericInterfaceContract c, address owner) returns (uint) {
        if (c.supportsInterface(Token.interfaceID)) {
            Token t = Token(c);
            return Token(c).allowance(owner, msg.sender);
        } else if (c.supportsInterface(SimpleToken.interfaceID)) {
            return 0;
        } else {
            throw;
        }
    }
}

Note:
Since the EVM does not provide any guarantees for the semantics of contracts, any such information returned by other contracts can only be used as a guideline.

Note2:
The convenient access to the interface ID via Token.interfaceID is not yet supported by solidity ( ethereum/solidity#1290 ).

Credits: This is the result of a discussion with @konradkonrad and @frozeman

@frozeman frozeman added the ERC label Nov 7, 2016

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented Nov 22, 2016

This would be particularly useful for ENS resolvers, and would allow removing the has() method, which implements an equivalent (ENS-specific) operation.

supportsInterfaces seems like a lot of extra overhead for relatively little gain over just calling the contract multiple times, however, especially given the low number of interfaces one is likely to need to check.

@frozeman frozeman changed the title from Pseudo-Introspection to Pseudo-Introspection, or standard interface detection Nov 25, 2016

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented Nov 29, 2016

A couple of other suggestions:

  1. interfaceID assumes the existence of a 'primary' interface, and doesn't seem a-priori terribly useful. Unless there's compelling use cases for it, I'd suggest dropping it.
  2. Making the interface ID the 32 bit XOR of all the function signatures the interface contains would make calculating it simpler, relying only on existing code to generate function hashes and simple arithmetic ops.
@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented Nov 29, 2016

Discussing this with Chris in the Solidity channel, he pointed out that an interface is more than its ABI; it's possible for two interfaces with the same set of function signatures to have different expectations of how they operate (especially since function signatures don't include parameter names!). So, the interface ID should be a pseudorandom value with low probability of colliding with any other interface ID; it doesn't need to bear a strict relationship to the source (and arguably, shouldn't, so people don't make assumptions about it being usable as some kind of test).

I agree - so ignore my earlier suggestions about constructing the interface ID from the constituent functions.

Edit: On the other hand, Go does 'duck typed interfaces' and generally doesn't have a problem; although you can have two interfaces with the same ABI but different intents, the chances of both of them being relevant in a given scenario seem vanishingly small. For instance, it's easy to avoid a collision in interface definitions for ENS resolvers, and nobody's going to ask an ENS resolver if it implements, say , a token-related interface.

@chriseth

This comment has been minimized.

Contributor

chriseth commented Nov 30, 2016

After some more discussion, I think we did settle on just using the xor of the function identifiers that are part of the interface as a canonical identifier.

@VoR0220

This comment has been minimized.

VoR0220 commented Dec 2, 2016

@chriseth @Arachnid I am of the belief that we should be either copying how Go does this or how Rust does this in regards to our interfaces. They should be their own type. Something looking like this:

contract C {
    interface I {
         foo(uint256) returns (uint256);
         bar(bool, string) returns (bool);
    }
}

contract A {
     struct MyStruct {
          function foo(uint256) returns (uint256);
          function bar(bool, string) returns (bool);
     }
     MyStruct _myStruct;
     D someContract;
     function a_call() {
          d.f(_myStruct); //passes in _myStruct as interface to D
     }
}

contract B {
  E _e; 
  D _d;

  function call_D() {
       _d.f(_e); //passes in E to D and thus will clear because functions align.
   }
} 

contract E {
    function foo (uint256) returns (uint256) {
       //some function
   }
   function bar (bool, string) returns (bool) {
     //some function
   }
} 
contract D is C {
     function f(_interface I) {
            switch type(_interface) { //switch on the types of objects passed into the interface (i think this should be mandatory to some extent)
                 case A:
                      uint a = _interface.foo(1);
                 case B: 
                       bool truth = _interface.bar(true, "something");
                 default: throw;
            }
     }
}

I'm not sure about xor-ing it...that may work, I'm just not sure I understand it yet, need to look. What I think we can do for the type assertion is to hash for these types based on their actual function opcodes that they are utilizing/generating on the stack portion. This will make it so that we can verify that only these contracts are calling externally and can make the interface quite a useful safety mechanism.

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented Dec 11, 2016

@VoR0220 I think what you're talking about is language design, and thus orthogonal to this EIP, which is mostly about how to implement the feature at the EVM level.

@VoR0220

This comment has been minimized.

VoR0220 commented Dec 12, 2016

I believe you might be correct. Will reread this in the morning.

@VoR0220

This comment has been minimized.

VoR0220 commented Feb 8, 2017

Okay. 2 Months later, I have reread it and with some help I think I understand the proposal better. Here's my question. How might we distinguish between different versions of a contract interface? Would that need to be included? Or would it be "Version independent"?

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 3, 2017

Here are the modifications that I would do in the standard:

1.- I would keep GenericInterface very simple with just one single method: supportsInterfaces(bytes32). I would not include the other two methods: supportsInterfaces and interfaceID.
2.- This interface should always return true when called with 0xf1753550 as a parameter. (This is the interface that implements supportsInterfaces(bytes32).
3.- This function should always return false when called with 0xff···ff parameter.

With this definition, You can test if a contract implements EIP165 or not by trying to call supportInterface for GenericInterface (Should always return true) and an Invalid interface (Should always return false). If an exception, out of gas or the results are not true/false, then this means that this contract does not implement EIP165

For saving gas, a single generic EIP165Cache contract can exist in the blockchain.

You can see an how this implementation would be here:

https://github.com/jbaylina/EIP165Cache/blob/master/contracts/EIP165Cache.sol

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented May 3, 2017

I broadly agree, although I'm not sure it's necessary to be able to test supportsInterface explicitly like that; if you call it and it throws, it's safe to say the method isn't supported (throwing is the default fallback behaviour for some time now).

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 3, 2017

@Arachnid I believe that there are some contracts (like wallets or normal addresses) that don't throw. They just do nothing.
In those old contracts, they may return false or true. What I am testing is that they return true in a "must Implement" interface and false in a "must not implement" interface. This way we guaranty that it will work in most (if not all) old contracts.

If you agree on this, I will make some tests with the most used contracts so that they return false. We will see if this "double check" is necessary or not.

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 3, 2017

@Arachnid Another question, I see that in the ENS implementation, the fingerprint is:

function supportsInterface(bytes4 _interfaceID) constant returns (bool);

instead of

function supportsInterface(bytes32 _interfaceID) constant returns (bool);

I think it's better to use bytes32 instead of bytes4 even if we use only 4 bytes. This will give space for future ampliations.

And may be it's too late now, but I think it would be good to also return true for thesupportsInterface interface:

0xf1753550 -> supportsInterface(bytes32)
0x01ffc9a7 -> supportsInterface(bytes4)

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented May 3, 2017

bytes4 was what was agreed on in the Solidity channel, but @chriseth never updated the draft accordingly. I don't think it's possible to change how many bytes we use without breaking backwards compatibility, so I'd vote for keeping it as bytes4.

There's certainly no harm in having contracts return true for the supportsInterface interface. I'll update the ENS contracts accordingly.

Since you're working on this, any interest in submitting a PR that formalises it?

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 3, 2017

@Arachnid: You mean a PR for the standard or for the ENS?

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented May 3, 2017

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 4, 2017

@Arachnid Ok, I'm going to prepare it!

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 26, 2017

I have the first draft of the Specification Here:
https://docs.google.com/document/d/16AC8gBylYgPE5zr9C4mz2fS1oYLHQf1o8sGjxmw8Juw/edit?usp=sharing

I have also created and tested the EIP165Cache contract Here:
https://github.com/jbaylina/EIP165Cache

Please, comment/correct in the doc before creating the formal PR.

@Arachnid

This comment has been minimized.

Collaborator

Arachnid commented May 26, 2017

Looks like a good start, but it would be much easier to leave comments against the PR than in a Google Doc (and they'll be preserved for posterity).

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 26, 2017

Ok. Then I'll Create the PR.

@jbaylina

This comment has been minimized.

Contributor

jbaylina commented May 26, 2017

@veox

This comment has been minimized.

Contributor

veox commented Jan 25, 2018

My previous comment on view/pure was that this is a compile-time distinction. It may be unenforceable at runtime (at least ATM). It's also Solidity-centric.


In the draft, is the example token supposed to be ERC20-compliant?.. There's no indication to that effect, and it does lack transferFrom(address,address,uint256), so I'd guess no; it would be nice, however, to explicitly state this fact somewhere ("A (non-ERC20) token"), so there's less chance someone makes an incorrect assumption.

@chriseth

This comment has been minimized.

Contributor

chriseth commented Jan 29, 2018

view will be enforced using staticall soon.

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Jan 29, 2018

@chriseth thanks for the note. Currently we are discussing the distinction between view and pure. This is not impacted by staticcall.

@Recmo

This comment has been minimized.

Recmo commented Jan 30, 2018

The cache could have a public function queryContract(address, bytes4) that bypasses cache and re-queries contract. This allows contracts that change their interface to eventually update caches.

There will be a period with stale data in the cache. In the case of a contract adding support for an interface, this should not matter much as the contract works as advertised by the cache. New functionality will become available eventually. In case of removing an interface, it can cause problems, but I don't think contracts removing an interface should have expectations that this will be a smooth transition.

Also:

  1. a privately controlled cache could allow the operator to override the cache. The operator can then, for example, flag ERC20 support on non-EIP165 tokens.
  2. a more adventurous cache could test the existence of balanceOf, totalSupply, allowance and speculatively flag support for ERC20Basic / ERC20.
@Recmo

This comment has been minimized.

Recmo commented Jan 30, 2018

@fulldecent In your token example you only claim support for ERC223, but it also supports ER20Basic. It would better illustrate the point of this EIP if it flags support for more interfaces:

    bytes4 constant EIP20Basic =
        bytes4(keccak256('totalSupply()')) ^
        bytes4(keccak256('balanceOf(address)')) ^
        bytes4(keccak256('transfer(address,uint256)'));
    
    bytes4 constant EIP20Optional =
        bytes4(keccak256('name()')) ^
        bytes4(keccak256('symbol()')) ^
        bytes4(keccak256('decimals()'));
    
    bytes4 constant EIP20 =
        EIP20Basic ^
        bytes4(keccak256('allowance(address,address)')) ^
        bytes4(keccak256('approve(address,uint256)')) ^
        bytes4(keccak256('transferFrom(address,address,uint256)'));
    
    bytes4 constant EIP223 =
        EIP20Basic ^
        EIP20Optional ^
        bytes4(keccak256('transfer(address,uint256,bytes)'));

    function IToken() {
        supportsInterface[EIP20Basic] = true;
        supportsInterface[EIP20Optional] = true;
        supportsInterface[EIP223] = true;
    }

(Alternatively it could be done by declaring more interface contracts and using inheritance.)

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Jan 31, 2018

@Recmo. Thank you, I will work this into the next version.

@Recmo

This comment has been minimized.

Recmo commented Jan 31, 2018

@fulldecent I've written a bunch already (source). I went with your inheritance approach.

@ALL: Would it make sense to have a canonical repository for these interface contracts somewhere? Perhaps as part of the ethereuem/EIPs repo. These interface contracts are currently being copy-pasted to all sorts of places, and different conflicting version are floating around. With EIP165 it will be especially important to be consistent, or else the identifiers will differ. It would also be nice to have them with full natspec documentation.

Long term it would be nice if 1) these canonical interfaces also define semantic requirements like 'sum of balances equals totalsupply' and 2) EIP165 becomes part of the ABI and Solidity gains native support for it; for example by auto generating the correct supportsInterface method.

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Jan 31, 2018

Right now we have a few solutions for what we are trying to solve:

165-VIEW 165-PURE 820
Can express if you implement an interface YES YES YES
Can change if you implement an interface YES NO YES
Can accurately cache results with a singleton contract POSSIBLE YES YES
Can accurately cache results without a singleton contract NO YES NO
Can delegate compliance to a separate contract NO NO YES
Uses function signatures as interface YES YES NO
Uses string as interface NO NO YES
Allows third-party to change interface implementedness POSSIBLE NO YES
@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Jan 31, 2018

VERSION 3: Added assembly parts


Also, we could just be a boss and do actual introspection on the blockchain from a contract.

  • This only works from Solidity
  • This exploits the fact that Solidity inserts function dispatch boilerplate at the beginning of all contracts
  • Function dispatch knows of all defined functions
  • This can return false negatives if the contract has some fancy assembly in its fallback function (i.e. it's own function dispatch)
pragma solidity ^0.4.19;

contract ContractIntrospector {
    enum Result {No, Yes, PleaseCallAgainToContinueSearching}
    mapping (address => mapping(bytes4 => bool)) private contractFunctionSelectorSupported;
    mapping (address => bytes4[]) public contractFunctionSelectors;
    mapping (address => uint) private searchProgress;
    mapping (address => bool) public doneSearching;

    function getFunctionSignature(string _function) external pure returns (bytes4) {
        return bytes4(keccak256(_function));
    }

    // Save lookup table in memory
    function getBytesForOpcodes() private pure returns (uint256[256] retval) {
        // Initialize bytesForInstruction
        // Read the Yellow Paper, find α for each opcode 
        for (uint i = 0; i < 256; i++) {
            retval[i] = uint256(-1); // Default return value
        }
        retval[0x00] = uint256(-1); // HALT
        retval[0x01] = 1 + 32*1; // ADD
        retval[0x02] = 1 + 32*1; // MUL
        //...
    }

    function getSize(address _addr) public view returns (uint256 _size) {
        assembly {
            _size := extcodesize(_addr)
        }
    }
    
    function getCode(address _addr, uint256 _start, uint256 _length)
        public
        view
        returns (bytes _code)
    {
        _code = new bytes(_length);
        assembly {
            extcodecopy(_addr, add(_code, 0x20), _start, _length)
        }
    }
    
    function howManyBytesCanWeProcessWorstCaseWithRemainingGas()
        private
        returns (uint256 _size)
    {
//TODO: implement this better        
        return 20;        
    }
    
    function doesContractSupportFunctionSelector (
        address _addr, 
        bytes4 _functionSelector
    )
        public
        returns (Result)
    {
        if (contractFunctionSelectorSupported[_addr][_functionSelector]) {
            return Result.Yes;
        }

        uint contractSize = getSize(_addr);
        uint start = searchProgress[_addr]; // starts at zero
        if (start >= contractSize) {
            return Result.No;
        }
        uint256[256] memory bytesForOpcode = getBytesForOpcodes();
        uint length = howManyBytesCanWeProcessWorstCaseWithRemainingGas();
        bytes memory code = getCode(_addr, start, length);
        uint ptr = 0;        
        while (ptr < length) {
            if (code[ptr] == 0x63) { // PUSH4
                bytes4 selector = code[ptr+1] | // TODO: is endian here correct?
                    code[ptr+2] << 8 |
                    code[ptr+3] << 16 |
                    code[ptr+4] << 24;
                contractFunctionSelectorSupported[_addr][selector] = true;
                contractFunctionSelectors[_addr].push(selector);
                if (selector == _functionSelector) {
                    searchProgress[_addr] = start + ptr;
                    if (start + ptr > contractSize) {
                        doneSearching[_addr] = true;
                    }
                    return Result.Yes;
                }
            }
            if (code[ptr] == 0x5b) { // JUMPDEST
                searchProgress[_addr] = uint256(-1);
                doneSearching[_addr] = true;
                return Result.No;
            }
            ptr += bytesForOpcode[uint256(code[ptr])];
        }
        if (ptr >= length) {
            searchProgress[_addr] = uint256(-1); // the maximum uint value
            return Result.No;
        }
        return Result.PleaseCallAgainToContinueSearching;
    }
}

Yeah, this is mostly a joke/though experiment. But maybe you were thinking it too, so just wanted to say it.

@Recmo

This comment has been minimized.

Recmo commented Jan 31, 2018

Haha, yes, I non-seriously considered decompiling too. (Btw, the current selector prequel has a gas cost linear in number of functions, but low constant cost is quite doable using a map, but would break decompiling approaches.)

Just to be clear. With 'heuristics' in my earlier comment I meant something similar to @jbaylina's noThrowCall to check for the existence of never-throw functions like owner, balanceOf, allowance, name, etc. I think such a heuristic would be able to distinguish the many of the current interfaces that don't support EIP165 yet.

I'm not a big fan of EIP-820. 1) It complicates deployment, which will complicate development and testing and hurt adoption. This standard is pretty much code-copy paste to implement, easy for adoption. 2) EIP820 in its current form relies on user-provided strings for ids. I foresee contracts incorrectly claiming to support the ERC20 interface. 3) It allows for other contracts to implement an interface on behalf of the contract, this adds an extra layer of complication to using interfaces on contracts as you now need to support a layer of indirection. This indirection can change at any moment, so you can not cache it. This seems like a very big gas cost overhead to me. In fact, this indirection can change mid-transaction. Finally, what happens if a malicious token contract 'claims' that its 'transfer' interface is implemented by an unrelated valuable token?

My favorite is 165-view. The only problematic caching case is when a contract drops support for a interface. (Adding can be solved by manually invalidating cache, which is trustless) But this is already a problematic case without EIP165. Contracts that plan to do this should say so in advance (lest developers will depend on an interface) and when they plan in advance they can also say not to rely on interface caches.

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Feb 4, 2018

Quick updates:

  • I need to hear from somebody who would vote on this ERC-165 as to their thoughts on which direction we should take.
  • That gives me a reasonable hope that my effort here can result in a win.
  • We can harmonize interface names with #820
  • Maybe we should separate interface names from XOR function selectors
  • I built an awesome Solidity decompiler, almost works. Does introspection, not pseudo
  • Use this.f.selector. It is way better
@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Feb 13, 2018

OK. I am going to go forward with writing this up for EIP165-VIEW variant. PR coming.

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Feb 13, 2018

@chriseth Responding to your offer to hand off this work, I have accepted the challenge and here is #881.

If you believe my effort should be canonical, would you please add a large note to the top of this issue:

THIS DISCUSSION MOVED TO #881

fulldecent added a commit to fulldecent/EIPs that referenced this issue Feb 13, 2018

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Feb 13, 2018

@Recmo Sorry, that link broke, can you please send again?

@fulldecent fulldecent referenced this issue Feb 15, 2018

Merged

ERC-165 Standard Interface Detection #881

7 of 8 tasks complete
@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Feb 15, 2018

Hello channel. #881 is very far along. I have described it faithfully as was discussed here ("165-VIEW" as above). As yet, I have seen no controversy in the 165-VIEW approach.

Previously there was an inconsistency with the 165-VIEW approach and the EIP165Cache from @jbaylina. I have resolved this by rewriting the cache.

Previously there was an open item from 165-PURE discussion about the gas cost of using an interface mapping versus using the PURE approach. Both implementations are shown and the gas costs are discussed.

Please support me by going to #881 and reacting with a party emoji 🎉 if you believe this EIP should be accepted. Acceptance is NOT final. The significance of acceptance is detailed in EIP-1:

If the EIP collaborators approve, the EIP editor will assign the EIP a number (generally the issue or PR number related to the EIP), label it as Standards Track, Informational, or Meta, give it status “Draft”, and add it to the git repository.

@fulldecent

This comment has been minimized.

Contributor

fulldecent commented Feb 15, 2018

@jbaylina,

The supporting documentation for RJ @VoR0220, Hudson @Souptacular and Griff @GriffGreen
being listed as authors in this standard are inside your Google Doc version at https://docs.google.com/document/d/16AC8gBylYgPE5zr9C4mz2fS1oYLHQf1o8sGjxmw8Juw/edit#heading=h.kkpgop5ftnhc

I do not have access to that history. FILE-> VERSION HISTORY -> PRIOR VERSIONS.

Would you please review this history and attest that these three individuals should be listed as authors?

If you recommendation a deletion, please suggest it here and at #881. If you can make the attestation, this will be helpful to document for posterity.

@nicksavers

This comment has been minimized.

Collaborator

nicksavers commented Feb 22, 2018

The PR for this in #881 has been merged

@mwherman2000

This comment has been minimized.

mwherman2000 commented Oct 13, 2018

FYI: On the NEO blockchain, this requirement was solved in the following way: https://github.com/neo-project/proposals/blob/master/nep-10.mediawiki

yinchang0124 added a commit to Jerry7379/PIE that referenced this issue Nov 20, 2018

pragma solidity ^0.4.24;
//Ownable ¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
contract Ownable{
    address public owner;

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function Ownable(){
        owner = msg.sender;
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    modifier onlyOwner(){
        require(msg.sender == owner);
        _;
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?newOwner¿?
    function transferOwnership(address newOwner)onlyOwner{
        if(newOwner != address(0)){
            owner =newOwner;
        }
    }
}

contract pigAccessControl is Ownable{

    /**
     *government¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
     *¿?¿?¿?¿?¿?¿?¿?pigcore¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
     */

    ///¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    //¿?¿?¿?¿?¿?
    address public governmentAdddress;
    User public user;

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    bool public paused = false;

    enum Role{Farm, Slaugthethouse, Logistics, Market}//¿?¿?

    struct User{
        address roleAddress;
        uint256 roleId;
        string location;
        Role role;
    }

    mapping (address => User) public FarmMap;
    mapping (address => User) public SlaughterhouseMap;
    mapping (address => User) public LogisticsMap;
    mapping (address => User) public MarkeMap;

    //¿?¿?government¿?¿?¿?¿?¿?¿?¿?¿?
    modifier onlyGovernment(){
        require(msg.sender == governmentAdddress);
        _;
    }

    //¿?¿?farm¿?¿?¿?¿?¿?¿?¿?¿?
    modifier onlyFarm(){
        require(msg.sender == user.roleAddress);
        _;
    }

    //¿?¿?slaughterhouse¿?¿?¿?¿?¿?¿?¿?¿?
    modifier onlySlaughterhouse(){
        require(msg.sender == user.roleAddress);
        _;
    }

    //¿?¿?logistics¿?¿?¿?¿?¿?¿?¿?¿?
    modifier onlyLogistics(){
        require(msg.sender == user.roleAddress);
        _;
    }

    //¿?¿?market¿?¿?¿?¿?¿?¿?¿?¿?
    modifier onlyMarket(){
        require(msg.sender == user.roleAddress);
        _;
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?government,¿?¿?¿?¿?¿?¿?government¿?¿?¿?
    //_newGovernment¿?¿?government¿?¿?¿?
    function setGovernment(address _newGovernment)external onlyOwner {
        require(_newGovernment != address(0));
        governmentAdddress = _newGovernment;
    }

    function newUser(uint256 ID, string location,Role role) returns(bool, address,Role,string){

        if(role == Role.Farm){
            //user = FarmMap[msg.sender];
            user.roleAddress = msg.sender;
            user.roleId = ID;
            user.location = location;
            user.role = role;
            FarmMap[msg.sender] = user;
        }else if(role == Role.Slaugthethouse){
            //user = SlaughterhouseMap[msg.sender];
            user.roleAddress = msg.sender;
            user.roleId = ID;
            user.location = location;
            user.role = role;
            SlaughterhouseMap[msg.sender] = user;
        }else if(role == Role.Logistics){
           // user = LogisticsMap[msg.sender];
            user.roleAddress = msg.sender;
            user.roleId = ID;
            user.location = location;
            user.role = role;
            LogisticsMap[msg.sender] = user;
        }else if(role == Role.Market){
            //user = MarkeMap[msg.sender];
            user.roleAddress = msg.sender;
            user.roleId = ID;
            user.location = location;
            user.role = role;
            MarkeMap[msg.sender] = user;
        }else{
            return (false,user.roleAddress, user.role,"the actor is not belong");
        }
        if(user.roleId != 0x0){
            return (false, user.roleAddress, user.role,"this ID has been occupied!");
        }
        user.roleAddress = msg.sender;
        user.roleId = ID;
        user.location = location;
        user.role = role;
        return (true, user.roleAddress, user.role,"Success");
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    modifier whennotPaused{
        require(!paused);
        _;
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    modifier whenPaused{
        require(paused);
        _;
    }

    //¿?¿?government¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?,¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function pause() external onlyGovernment whennotPaused{
        paused == true;
    }

    //¿?¿?government¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?,¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function unpause() public onlyGovernment whenPaused {
        paused == false;
    }
}

///¿?¿?ERC721¿?¿?¿?¿?¿?
contract ERC721 {
    // Required methods
    //¿?¿?¿?¿?token¿?¿?¿?
    function totalSupply() public view returns (uint256 total);
    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function balanceOf(address _owner) public view returns (uint256 balance);
    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function ownerOf(uint256 _tokenId) external view returns (address owner);
    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function transfer(address _to, uint256 _tokenId) external;

    // Events
    event Transfer(address from, address to, uint256 tokenId);

    function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);

    // ERC-165 Compatibility (ethereum/EIPs#165)
    function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}

/// ¿?¿?pig¿?¿?¿?¿?¿?¿?¿?pig¿?¿?¿?¿?¿?
contract pigBase is pigAccessControl{

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?Birth¿?¿?¿?
    event Birth(uint256 pigID, address owner, uint64 birthTime, uint256 breed, uint256 weight, int8 gender);
    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    event Transfer(address from, address to, uint256 tokenId);

    /**
     * pig¿?¿?¿?
     */
    struct pig{
        //¿?¿?¿?¿?¿?
        address currentAddress;
        //¿?¿?¿?¿?
        uint64 birthTime;
        //¿?¿?
        uint256 breed;
        //¿?¿?
        uint256 weight;
        //¿?¿?
        int8 gender;
    }

    //¿?¿?¿?¿?¿?¿?pig¿?¿?¿?¿?¿?,¿?¿?pig¿?ID¿?¿?¿?¿?¿?¿?¿?¿?
    pig[] pigs;

    //¿?pigID¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    mapping(uint256 => address) public pigIndexToOwner;
    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    mapping(address => uint256) public ownershipTokenCount;

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function _transfer(address _from, address _to, uint256 _tokenId)internal{
        ownershipTokenCount[_to]++;
        // ¿?¿?¿?¿?
        pigIndexToOwner[_tokenId] = _to;
        // ¿?¿?¿?¿?¿?¿?¿?¿?¿?0x0¿?¿?¿?
        if(_from != address(0)){
            ownershipTokenCount[_from]--;
        }

        //¿?¿?¿?¿?¿?¿?¿?¿?
        Transfer(_from, _to, _tokenId);
    }

    /**
     * ¿?¿?¿?¿?newpig¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
     * ¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?,¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
     * ¿?¿?¿?Birth¿?¿?¿?Transfer¿?¿?¿?
     */
    function createPig (
        uint256 _breed,
        uint256 _weight,
        int8 _gender
        ) external returns (uint256) {

        pig memory _pig = pig({
            currentAddress : msg.sender,
            birthTime : uint64(now),
            breed : _breed,
            weight : _weight,
            gender : _gender
        });

        uint256 newPigID = pigs.push(_pig) - 1;

        // ¿?¿?Birth¿?¿?
        emit Birth(newPigID, msg.sender,uint64(now), _breed, _weight, _gender);

        // ¿?¿?¿?¿?¿?¿?¿?¿?¿?Transfer¿?¿?
        // ¿?¿?ERC721¿?¿?
        _transfer(0, msg.sender, newPigID);
        return newPigID;
    }
}

/// ¿?¿?¿?¿?¿?KittyBase¿?ERC721¿?¿?¿?ERC721¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
contract pigOwnership is pigBase,ERC721{

    //¿?¿?ERC721¿?Name¿?symbol¿?¿?¿?¿?¿?¿?¿?Token
    string public constant name = "Pig¿?s Life";
    string public constant symbol = "PIE";

    bytes4 constant InterfaceSignature_ERC165 =
    bytes4(keccak256('supportsInterface(bytes4)'));

    bytes4 constant InterfaceSignature_ERC721 =
    bytes4(keccak256('name()')) ^
    bytes4(keccak256('symbol()')) ^
    bytes4(keccak256('totalSupply()')) ^
    bytes4(keccak256('balanceOf(address)')) ^
    bytes4(keccak256('ownerOf(uint256)')) ^
    bytes4(keccak256('approve(address,uint256)')) ^
    bytes4(keccak256('transfer(address,uint256)')) ^
    bytes4(keccak256('transferFrom(address,address,uint256)')) ^
    bytes4(keccak256('tokensOfOwner(address)')) ^
    bytes4(keccak256('tokenMetadata(uint256,string)'));

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?ERC721¿?ERC165¿?¿?
    function supportsInterface(bytes4 _interfaceID)external view returns(bool){
        return((_interfaceID == InterfaceSignature_ERC721) || (_interfaceID == InterfaceSignature_ERC165));
    }

    // ¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    // _currentAddress ¿?¿?¿?¿?¿?¿?¿?¿?
    function _owns(address _currentAddress, uint256 _tokenId) internal view returns (bool){
        return pigIndexToOwner[_tokenId] == _currentAddress;
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function balanceOf(address _owner)public view returns(uint256 count){
        return ownershipTokenCount[_owner];
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?ERC-721¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function transfer(address _to, uint256 _tokenId) external {
       // ¿?¿?¿?¿?¿?0x0
        require(_to != address(0));
        require(_to !=address(this));
        // ¿?¿?¿?¿?¿?¿?¿?¿?
        require(_owns(msg.sender,_tokenId));
        // ¿?¿?¿?¿?¿?¿?¿?Transfer¿?¿?
        _transfer(msg.sender, _to, _tokenId);
        pig storage Pig = pigs[_tokenId];
        Pig.currentAddress = pigIndexToOwner[_tokenId];
       // pigs.push(Pig);
        emit Transfer(msg.sender, _to, _tokenId);
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?
    function totalSupply()public view returns(uint){
        return pigs.length;
    }

    //¿?¿?¿?¿?¿?¿?¿?¿?
    function ownerOf(uint256 _tokenId)external view returns(address owner){
        owner = pigIndexToOwner[_tokenId];
        require(owner != address(0));
    }

    // ¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?
    function tokensOfOwner(address _owner)external view returns(uint256[] ownerTokens){
        // ¿?¿?_owner¿?¿?¿?¿?¿?¿?¿?
        uint256 tokenCount = balanceOf(_owner);

        if(tokenCount == 0){
            // ¿?¿?¿?¿?¿?¿?¿?¿?¿?
            return new uint256[](0);
        }else{
            //¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?¿?result¿?¿?¿?¿?tokenCount
            uint256[] memory result = new uint256[](tokenCount);
            // ¿?¿?¿?¿?¿?¿?¿?¿?
            uint256 tolalPigs = totalSupply();
            // ¿?¿?¿?¿?¿?¿?
            uint256 resultIndex = 0;
        }

         // ¿?¿?ID¿?1¿?¿?¿?¿?
        uint256 pigID;
        // ¿?1¿?¿?¿?¿?¿?¿?¿?¿?¿?tolalPigs
        for(pigID =0; pigID < tolalPigs; pigID++){
            // ¿?¿?¿?¿?pigID¿?¿?¿?¿?¿?¿?¿?_owner
            if(pigIndexToOwner[pigID] == _owner){
                // ¿?¿?¿?¿?¿?pigID¿?¿?result¿?¿?resultIndex¿?¿?
                result[resultIndex] = pigID;
                resultIndex++;
            }
        }
        return result;
    }

}

contract pigSeparation is pigOwnership{
//¿?¿?¿?¿?¿?¿?
//¿?¿?¿?¿?¿?¿?
}

///¿?¿?¿?
contract pigCore is pigOwnership{

    function getPig(uint256 _id) external view returns(
        address currentAddress,
        uint64 birthTime,
        uint256 breed,
        uint256 weight,
        int8 gender
    ){

        pig storage Pig = pigs[_id];
        currentAddress = address(Pig.currentAddress);
        birthTime = uint64(Pig.birthTime);
        breed  = uint256(Pig.breed);
        weight = uint256(Pig.weight);
        gender  = int8(Pig.gender);

    }

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment