web3.py: Invoke ambiguous contract functions
What was wrong?
If a contract has multiple functions of the same name, and the arguments are ambiguous, say:
contract AmbiguousDuo {
function identity(uint256 input, bool uselessFlag) returns (uint256) {
return input;
}
function identity(int256 input, bool uselessFlag) returns (int256) {
return input;
}
It is currently impossible to call the identity function on that contract for positive numbers, because web3 cannot identify which one you want:
contract.functions.identity(1).call()
How can it be fixed?
Add a way to unambiguously call a specific method (maybe by providing the function signature). Something like:
identity_unsigned = contract.functions['identity(uint256,bool)']
identity_unsigned(1, True).call()
identity_signed = contract.functions['identity(int256,bool)']
identity_signed(1, True).call()
It should support all these options for identifying the function:
- Full Signature:
contract.functions['identity(int256,bool)']
- Byte selector (first 4 bytes of the hash of the full signature), in the form of:
bytes
–contract.functions[b'\x8e\xab\x23\x03']
int
(writable as a hex literal in python) –contract.functions[0x8eab2303]
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 2
- Comments: 32 (21 by maintainers)
@veox great write-up and really good stuff for us to be aware of because I can see us eventually having some of the following present in the ecosystem.
@veox Thanks for the detailed write up, makes sense! I will stick to jason’s advice for erroring out early incase of collisions.
TL;DR: Raising an exception on collisions in the ABI would be a safe bet, and a wise thing to do. 😃
It is an edge case, so should be rare unless intentional. My main concern is with the “intentional” part: it’s not about what
web3
implementations are willing to support, but what is going to be “fed” to them, in some cases maliciously.Very OT:
Yes, I’m talking about LLL mainly. 😃
The earliest workflow for me was:
contract.abi
andcontract.bytecode{,_runtime}
.These days, the order is reversed. So, I do use
solc --hashes
to generate the same selectors as everybody else, and I place these selectors in the program, - but that’s just a convenience, for the sake of compatibility with existing tools.This is all very flexible, precisely because there are no assumptions of, say, the
bytecode
having been produced by the same tool as theabi
.For now, everyone’s doing just fine pigeonholing themselves into what’s already supported. No need to spread thinner. 😃
Rabbit-hole OT:
According to the
web3.0
zeitgeist, a program would not have both:because Solidity does not allow collisions in function selectors; the ecosystem is optimised for the most popular language, Solidity; “a contract” mentioned in an abstract statement will likely be Solidity. Such a program is inconceivable!..
An LLL program would check the length of
CALLDATA
, plus maybe the first four bytes of it, and run some macro based on those checks. So, it’s definitely conceivable.Where this is going I don’t know, but I’m sure that a general-purpose computer will always present ways to dig ourselves in deeper. 😄
Somewhat OT: “Solidity does X” shouldn’t be an argument. There are other languages, too. 😉 (Some of them don’t even have the notion of signatures/selectors…)
EDIT: Well, at least when discussing abstract
contract
s that are being created from user-provided ABI specifications. Checking for collisions while sanitising/validating the ABI would seem desirable indeed. 😃Dropping these two is fine by me, if we explicitly check for these conditions in the
abi
passed into the contract and error out when trying to create the contract object, on failure.Update: I just started working on this. Posting here to make sure that no one else is working on this. I don’t want to duplicate work!
I really like this (but they shouldn’t sit inside
contract.functions
where they might conflict with the contract’s functions).One trick is how do we help people do something useful with the returned functions in an iterator. I guess we add each function’s abi to that ContractFunction instance, so users can inspect the types and decide how to filter them further…
That’s not me necessarily saying we need
is_unique
in this particular API. I’m not sure exactly what good it would be, since all thefind_*
functions will be collections, and all theget_*
functions will be exactly one function. So is_unique is trivially implementable by users as:Not exactly.
ContractFunction
is really the set of all functions that match the given function name. So it is arguably a bit misnamed (since it implies exactly one function match). So you could find outis_unique
by checking if more than one function has that same name.It’s okay to fail out if
contract.abi
is not set, hopefully with a nice error message (as requested in #698).@carver I wanted to ask you if the API was finalized but then I’d thought to myself that I’ll get the basic implementation out and then we could rename/add functionality as needed. In my first take I want to implement the following:
piper merriam’s suggestion seems like it’ll need another class to implement the functionality. I’ll try to implement that too if time permits(but not in my first attempt).
Good to know! This whole feature suite is aimed at power users, so I’m definitely curious to explore it a bit with you.
I don’t understand something though: the default selector should be pretty good at differentiating between calls with different numbers of arguments. Why doesn’t something like this work for you?
Nope, just suggesting what you already proposed (full signature) with the addition of support for 4byte selector.
No, I just mean supporting
contract.functions['0xabcd1234']
assuming0xabcd1234
was one of the 4byte selectors for a contract functions. Should be quite trivial to implement.