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
Invoke ambiguous contract functions #775
Comments
might be nice to support both the full function signature and the 4byte selector in those lookups. |
Also probably worth some extra convenience APIs surrounding this. >>> contract.functions.find_by_name('identity')
[<Function identity(int256)>, <Function identity(uint256)>] |
Hm, was I suggesting something different? Oh, maybe you thought I meant you could just specify the one type you cared about, like:
Yeah, that too, but I was punting because it requires an even heavier api (since you have to also specify the ABI definition somehow). |
Nope, just suggesting what you already proposed (full signature) with the addition of support for 4byte selector.
No, I just mean supporting |
🤦♂️ Ah, of course: given that the function info was already specified in the abi (just like with all these other selectors). |
Hm, and we're back to inferring hex vs text strings. Solidity doesn't allow a function starting with |
ok, how about we don't support it with item lookups. contract.functions['identity'] # only allow function names here.
contract.functions.get_by_signature('identity(uint256') # only lookup based on signature
contract.functions.get_by_selector('0x1234abcd') # only lookup by selector. We could allow for signature lookups using Also, it might be good to support a full API like the following and make use of it internally.
Alternative names for
|
I really like this (but they shouldn't sit inside contract.all_functions_by_selector('0xac37eebb')
contract.all_functions_by_selector(0xac37eebb)
contract.all_functions_by_selector(b'\xac7\xee\xbb')
contract.function_by_selector('0xac37eebb')
contract.function_by_selector(0xac37eebb)
contract.function_by_selector(b'\xac7\xee\xbb')
contract.all_functions_by_name('identity')
contract.function_by_name('identity') # <-- contract.functions.identity aliases to this
contract.all_functions_by_signature('identity(uint256)') # <-- this one seems a little silly, I don't know how the solidity compiler handles a conflict here... but I guess just in case it doesn't hurt to include it.
contract.function_by_signature('identity(uint256)')
contract.all_functions_by_args(123)
contract.function_by_args(123) 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... |
Yes, please. ^_^ A case I've had (with LLL, so maybe not so relevant to most) is testing several same-name functions from a contract's ABI where the argument list gets ever-longer (less arguments implied). The proposed improvement is very far up the pipe from where I'm at now; but "it would be great" if I could
in a test matrix that mutates the This example is slightly convoluted, but I hope it demonstrates the possible utility of |
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? args = (1, True, 'str', b'bytes')
for last_arg_idx in range(len(args)):
range_end = last_arg_idx + 1
contract.functions.approve(*args[:range_end]).transact(...) |
That example of mine was not only "convoluted", but also "contrived": I'm not actually doing this (yet). :D (EDIT: To clarify: I've gone with "change the name" instead, i.e. the extra-argument function is called The snippet you provide would work just fine for testing the functions with their correct arguments and data, i.e.: function approve(uint256);
function approve(uint256,bool);
function approve(uint256,bool,string);
function approve(uint256,bool,string,bytes); with a total of 4 tests. What I wrote about should allow for 16 tests: every selector also tested with data that would have been passed to the other selectors in "normal" use. This gets even hairier if you imagine, say, ERC-20 getting "ported" to same-name approach: function transfer(address,uint256); // ERC-20 transfer()
function transfer(address,address,uint256); // ERC-20 transferFrom() In this case, the "extra non-implied argument" is prepended, not appended, to the argument list. Again, these examples are contrived, and probably "don't look like the best approach", precisely because I'm not trying to solve an actual problem with them. Maybe I better keep quiet now. (Having the ABI description available on the |
The following API improvements come to mind. >>> fn = contract.functions.transfer
>>> fn.is_unique
False
>>> fn.selectors
["0x1234abcd", "0xdeadbeef"]
>>> fn.selector
# exception: Multiple selectors available.
>>> fn.abis
[{...}, {...}]
>>> fn.filter_by_args(my_address, 12345)
[<Function transfer(address,uint256): 0x1234abcd]
>>> exact_fn = fn.get_by_args(my_address, 12345)
>>> exact_fn.is_unique
True
>>> exact_fn.selector
"0x1234abcd"
>>> exact_fn.abi
{...} And probably something like this too. >>> contract.all_functions
[<Function transfer()>, <Function transfer(address)>, <Function withdraw()>, ...]
>>> contract.all_functions.filter_by_name('transfer')
[...]
>>> contract.all_functions.filter_by_name('transfer').get_by_args(my_address, 12345)
<Function transfer(address,uint256)> |
I'll take a stab at this over the weekend. |
Thanks, @voith ! Note that the API here doesn't seem finalized. So there may be quite a bit of churn in the PR. (By posting a PR, you're implicitly proposing a final API) Alternatively, you could propose a summarized API here for consensus before trying to polish off an implementation. |
@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.
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). |
@carver One question, At the moment I'm not super familiar with how contract function names and signatures are fetched(I will figure it out once I start), but from my brief look I can see that it is inferred from the |
It's okay to fail out if |
@voith re needing another class. +1 to pushing that out to a subsequent PR. Lets ignore the idea of chaining these filters for now and just implement the top level API, after which we can see about extending it to somthing where you can chain the filtering methods together. Also, my naming suggestion would be |
I made this statement without having a complete Knowledge about the existing codebase, So I might be wrong there. @pipermerriam In fact I gave a thought to the API improvement that you've proposed. >>> fn = contract.functions.transfer
>>> fn.is_unique
False This reminds me of the
This again is similar to the API that @carver proposed. |
I think we could drop the |
I actually think we could drop the |
Not exactly. |
That's not me necessarily saying we need funcs = find_*(...)
unique = (len(funcs) == 1) |
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 have a basic working version of this which I'll submit either today or tomorrow. But I have a few questions before I submit.
I used the following code to test case 2.
both function selectors evaluate to 0x00000000. (I borrowed these names from the |
Dropping these two is fine by me, if we explicitly check for these conditions in the |
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 |
If that's the case then I assume that collision of selectors would not be an issue for such languages. So should we/Shouldn't error out strictly when collisions are found in the ABI?? OR does web3 plan to support ABI generated by such languages? Also @veox can you name some languages that don't have the notion of selectors. I've heard about |
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 Very OT: Yes, I'm talking about LLL mainly. :) The earliest workflow for me was:
These days, the order is reversed. So, I do use This is all very flexible, precisely because there are no assumptions of, say, the
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 // selector collision: both 0x00000000
function left_branch_block(uint32);
function overdiffusingness(bytes,uint256,uint256,uint256,uint256); 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 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. :D |
@veox Thanks for the detailed write up, makes sense! I will stick to jason's advice for erroring out early incase of collisions. |
@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.
|
Closed by #841 |
What was wrong?
If a contract has multiple functions of the same name, and the arguments are ambiguous, say:
It is currently impossible to call the identity function on that contract for positive numbers, because web3 cannot identify which one you want:
How can it be fixed?
Add a way to unambiguously call a specific method (maybe by providing the function signature). Something like:
It should support all these options for identifying the function:
contract.functions['identity(int256,bool)']
bytes
--contract.functions[b'\x8e\xab\x23\x03']
int
(writable as a hex literal in python) --contract.functions[0x8eab2303]
The text was updated successfully, but these errors were encountered: