-
Notifications
You must be signed in to change notification settings - Fork 444
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
ContractProtocol: support for overloaded functions in ABI #587
ContractProtocol: support for overloaded functions in ABI #587
Conversation
- Add language to code blocks - Replace Projects that are using lib to appropriate link to wiki. - Add myself to contributors list. - Some style enhancements.
…a for ABI.Element.Function and Constructor;
…in a JSON file to be attached to the project but there was none;
… input parameters
…tion.signature` (e.g. `getData(bytes32)`) - fatal error is thrown as ABI is invalid; Maybe it's worth replacing fatalError with just dropping all values but the first one from the saved array.
continue | ||
/// ABI cannot have two functions with exactly the same name and input arguments | ||
if (methods[function.signature]?.count ?? 0) > 1 { | ||
fatalError("Given ABI is invalid: contains two functions with possibly different return values but exactly the same name and input parameters!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something I'm not sure about.
As an alternative solution, we could remove all duplicates and leave only the first or the last entry of some function in question. But then we won't be able to guarantee that attempting to encode/decode functions' input parameters and return values will work - that will depend on the ABI that was used during the initialization of EthereumContract
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not my story, maybe @mloit have something to say?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's generally bad to ignore issues and try to push forward based on some assumption or rule. I'm also not a fan of error reporting via halting the program either (Precondition, FatalError, etc). I would much prefer to throw an exception. I realize that's not necessarily possible here, as that might be a breaking change. So for the time being, I would stick with the halt as it is, I think that is far safer than continuing on and potentially creating an incorrect transaction that will cost someone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll consider implementing this check within init
and maybe declaring this variable, and other similar ones, as let
instead.
This way we can return nil
from initializers or throw an error with a detailed description.
All of these variables will be most likely accessed at least once, and it's unlikely there will be so huge smart contract that its ABI will take significant time to process. That's just another point in favor of changing all lazy var
s into let
(or at least not lazy).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Speaking about a let
one, private variable required here with computed wrapper, because in reference types let
will not guarantee constancy of value under it, it'll just guarantee the constancy of reference to it, so it could be changed occasionally by a user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it doesn't apply in our case and here's why:
- I've marked all variables as
public let
excepttransactionOptions
andaddress
. All collections are immutable now. - All types used in the following collections are
struct
s:abi
,methods
,allMethods
,events
,allEvents
.constructor
is also struct. Thus, if anyone extracts it to a variable it will be a full copy. - Moreover - all types under
ABI.Element
have their internal variables declared aslet
which is good and must stay this way until web3 goes extinct.
So to recap - I do not see the actual issue here with marking variables as public let
. If I didn't get the idea of your message let's discuss it.
@@ -16,7 +16,7 @@ extension web3 { | |||
|
|||
/// Web3 instance bound contract instance. | |||
public class web3contract { | |||
var contract: EthereumContract | |||
private(set) public var contract: EthereumContract |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another turning point.
I'm not sure of the reasons why var contract: EthereumContract
wasn't initially public. It holds data that could be useful for developers and that is first of all decoded and filtered ABI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've looked at whole web3contract
implementation why is it even variable? As i've seen in code there's none assigning to its happening except of init. Should we made it constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I'll take a look at this and other variables in web3contract
and will propose changes making them constant if no new question will pop up during this additional review.
I wasn't sure if it should stay as var
but also wanted to make it let
. It's good that you suggested making it a constant 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Marked contract
and web3
as public let
.
In favour of using new DocC tools that we're presented by Apple this summer please add some short code example how to use new contract deployment from user point of view. Please add it to the very first ABI declaration (or to that type which is the one with what user will iterate to make contract deployment. As example of such please take a look at This allows us to give a user some code example docs in convenient place (Xcode API reference palette). |
Will do it later today. |
private(set) public lazy var methods: [String: [ABI.Element.Function]] = { | ||
var methods = [String: [ABI.Element.Function]]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like computed variable, so there's could not be set modifier, coz we actually can't set it anyhow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not relevant anymore. Please, see the latest version of EthereumContract
.
public var transactionOptions: TransactionOptions? = TransactionOptions.defaultOptions | ||
public var address: EthereumAddress? = nil | ||
|
||
var _abi: [ABI.Element] | ||
public let abi: [ABI.Element] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just reminder that constant in reference type makes constant only the reference itself, not its values instead. So if it's required to deny user to modifying this filed we're should consider a private property with a public [computed] wrapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I get your point.
The EthereumContract
was changed to class
from struct
.
All variables except address
are marked as public let
and thus cannot be modified.
All structs under ABI.Element
are having their internal variables declared as public let
(which is super good).
So there is no way for user of this class to modify something.
And also I'd be happy to hear some suggestion about name convention. Like this is definitely not a patch, but how bad it'll be to made 2.7.0 release? |
I think it's fine to make a 2.7.0 release. |
…sions to a separate file from ContractProtocol.swift
…, getEvents and getConstructor functions added; chore: Added new error type for ABI filtering - ABIError.invalidFunctionOverloading that is thrown if array of [ABI.Element] contains two functions with same name and input parameters;
…o DefaultContractProtocol. The DefaultContractProtocol is inherited by EthereumContract;
…act; ContractProtocol 'init' now throws instead of returning 'nil';
@mloit @yaroslavyaroslav I'm not sure if 2.7.0 will be a good place for these changes as there are, not that many, but still breaking changes that can block people from moving on until they update their projects. I'd make it part of 3.0.0 (or some release candidate of 3.0.0). I think since no one has reported any of the issues that were fixed here it'll be good to make it part of 3.0.0. |
@yaroslavyaroslav But before we must decide something about SwiftLint that was added in these PRs (that look very similar): |
…tion.signature` (e.g. `getData(bytes32)`) - fatal error is thrown as ABI is invalid; Maybe it's worth replacing fatalError with just dropping all values but the first one from the saved array.
…sions to a separate file from ContractProtocol.swift
…, getEvents and getConstructor functions added; chore: Added new error type for ABI filtering - ABIError.invalidFunctionOverloading that is thrown if array of [ABI.Element] contains two functions with same name and input parameters;
…o DefaultContractProtocol. The DefaultContractProtocol is inherited by EthereumContract;
…act; ContractProtocol 'init' now throws instead of returning 'nil';
…omForEventPresence (typo in word Presence)
@yaroslavyaroslav Finally rebased to |
Tests fail to run. Will check this. |
… happen synchronously before test cases run. Fixed by using `func setUp() async throws` instead of `class func setUp()`.
// check to see if we need to run the one-time setup | ||
if isSetUp { return } | ||
isSetUp = true | ||
override func setUp() async throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yaroslavyaroslav This is an important fix.
Because of the nature of async/await the blockchain instance that is deployed during testing doesn't get populated with new transactions before(!!) the actual test is executed - it all happens in parallel.
That led to CI failing because of gas estimation failure, which failed because of oracle failing to get the gas price in suggestGasFeeLegacy()
.
So now, before each test this function will run, but now it's synchronous.
So when the first test function will be executed by CI it will populate the blockchain instance with transactions, and the next test case will not add new transactions because of the condition on the line 18:
if block >= 25 { return }
@JeneaVranceanu so I'm about to merge it into right away, are you ok with that? |
Yes, let's do that. |
@yaroslavyaroslav I've some more updates I'd like to push but these will go as a continuation of this PR in order to avoid conflicts. Here is a preview - JeneaVranceanu#10 |
Thanks! |
What does this PR introduce?
TL;DR
func deploy
inContractProtocol
- wrong constructor was used to encode parameters;ContractProtocol
;ABI.Element.Function/Constructor
now have their own dedicatedencodeParameters
anddecodeInputData
.ABI.Element.Function
also has it's owndecodeReturnData
.Detailed description
In a solidity smart contract, we could have two functions with similar names but different input arguments.
It's basically function overloading.
The previous implementation of
EthereumContract
and declaration of protocolContractProtocol
were not supporting overloaded functions. If we had one two functions calledmyFunction
the one that will be decoded last will be the only one that will be saved inmethods
dictionary andallMethds
array as it will replace previously decoded one and mapped to name without input parameters.New implementation makes sure:
[String: [Function]]
;myFunction
) but also to a name with parameters' types (e.g.myFunction(uint256)
), and to function signature - 4 bytes in HEX, lowercased with0x
prefix (e.g.0xe164f2a0
).The list of breaking changes.
ContractProtocol
allMethods
type changed from[String]
to[ABI.Element.Function]
;allEvents
type changed from[String]
to[ABI.Element.Event]
;EthereumContract
struct
but is aclass
.Also, documentation was added to
ContractProtocol
.How deploy function changed?
Another important change in
ContractProtocol
:deploy
function expectsconstructor
argument. This is the constructor of the smart contract you are attempting to deploy!Before:
After:
This isn't a constructor that was decoded during the initialization of
EthereumContract
instance that was used previously. This is something that caught my eye as it was logically incorrect. Previously all deployment transactions were attempting to encode constructor parameters for the wrong constructor. Tests worked only because they were using constructors with no input arguments. There is one test where input arguments are used but it worked only because decoded ABI is having a constructor that has the same input arguments, in the same order as the bytecode of a smart contract that is being deployed.Now both constructor and parameters must be passed in to the call of
func deploy
in order to call the constructor with parameters.