Skip to content

feat: remote tests mocking #701

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

Conversation

JeneaVranceanu
Copy link
Collaborator

@JeneaVranceanu JeneaVranceanu commented Dec 7, 2022

The reason for this PR: tests in #685 fail due to the Kovan testnet being deprecated and it requires a redeploy of all smart contracts we use for testing on actually running remote networks.

What is introduced in this PR?

  • Tests for ST20 and SecurtyToken completely rewritten to support mocking of network responses;
  • Web3.Eth implements protocol IEth. IEth protocol has default implementation for all network calls like sendTransaction, getBalance etc;
  • Web3.Eth is no longer references Web3 instance that created Web3.Eth instance (meaning there will be no memory leaks);
  • ContractProtocol extended with a new function that can guess and return ABI.Element.Function based on CodableTransaction.data without the need for developers to extract the method ID each time;
  • ST20 and SecurityToken tests no longer require network connection and thus no deployed smart contracts as well. We do not depend for these tests on a testnet like Goerli or Kovan, or any other;
  • ST20 and SecurityToken tests take 0.22 seconds to complete;
  • Added ABIEncoder tests for dynamic types string and byte. More tests will be added in upcoming PRs.

Screenshot 2022-12-07 at 11 08 17


I've tried to use mock or make it easy to redeploy smart contracts on a new testnet.

Mocking libraries are not working in Swift as they do in other languages like e.g. Java. Swift has limitations on reflection and we cannot easily mock function calls and especially when these functions are in the extension block. I've tried using https://github.com/birdrides/mockingbird/ but in the end it failed in a sense that it will not allow to mock the parts we need due to how our code is written + Swift reflection limitations.

Another solution was to write "helper" functions that will deploy smart contracts on a selected chain. It has the following downsides:

  • cannot rely on a testnet being always available;
  • cannot rely on a network connection;
  • must setup for each individual chain an address and deposit some Ether to it;
  • must make sure there is enough balance (in a long run) on the addresses we use so that tests do not fail;
  • in a case of ST20 implementation we cannot test if the actual function call of a smart contract was created correctly (we can do so with the suggested changes in this PR).

The con of this update is that we have to implement mocks ourselves. It doesn't look difficult for now.

@JeneaVranceanu JeneaVranceanu added the enhancement New feature or request label Dec 7, 2022
@JeneaVranceanu JeneaVranceanu self-assigned this Dec 7, 2022
func callTransaction(_ transaction: CodableTransaction) async throws -> Data {
onCallTransaction?(transaction) ?? Data()
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need any other function to mock we just add it here.

switch function.name {
case "investorCount":
return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedNumberOfInvestors] as [AnyObject])!
// case "investors":
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling try await securityToken.investors() crashes.
The ABI and Swift implementations do not match. In ABI it expects uint256 argument as I recall but in Swift we try to call just investors function without arguments.

TODO: must fix the implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo or Fixme? I mean do you planning to fix it within this PR or is it a task for some better times?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not with this PR. I'll push a separate one as it's unrelated issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed commented code.
Fixing will be done in another PR.

/// Given the transaction data searches for a match in ``ContractProtocol/methods``.
/// - Parameter data: encoded function call used in transaction data field. Must be at least 4 bytes long.
/// - Returns: function decoded from the ABI of this contract or `nil` if nothing was found.
func getFunctionCalled(_ data: Data) -> ABI.Element.Function?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My dream for 4.0.0 is to create a set of Ethereum only data types that will be accounts all that special cases.

Must be at least 4 bytes long.

Because while we're using some advantages of swift strong typed system in 3.0.0 we're still missing a lot of them, treating those Ethereum rules in a python way, by leaving notes to our future selves.

Nothing to say about rewriting ABI module to generics as well.

Comment on lines +342 to +345
public func getFunctionCalled(_ data: Data) -> ABI.Element.Function? {
guard data.count >= 4 else { return nil }
return methods[data[0..<4].toHexString().addHexPrefix()]?.first
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see your dirty hack here. This is somewhat that I'd like to be implemented as a protocol. But again this is dream not even about current century.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that dirty I'd say.
We have an array of bytes, we get the first 4 of them and then look them up in the dictionary.
It's pretty straightforward.

Comment on lines +20 to +26
override func setUp() async throws {
web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken)
ethMock = Web3EthMock(provider: web3.provider)
web3.ethInstance = ethMock
st20token = ST20.init(web3: web3, provider: web3.provider, address: .contractDeploymentAddress())
securityToken = SecurityToken.init(web3: web3, provider: web3.provider, address: .contractDeploymentAddress())
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong: Is the all mocking happening just here, or I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the setup for mocking. The thing that is identical for all tests.
But each individual test mocks callTransaction function and is able to intercept the incoming requests and decide what to return in each separate test.

switch function.name {
case "investorCount":
return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedNumberOfInvestors] as [AnyObject])!
// case "investors":
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo or Fixme? I mean do you planning to fix it within this PR or is it a task for some better times?


let investorsCount = try await securityToken.investorCount()
XCTAssertEqual(investorsCount, expectedNumberOfInvestors)
// XCTAssertEqual(investors, expectedInvestors)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments should be dropped though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely not coming with this PR, just wanted to highlight that we have issues with either ABI or the implementation.
One of the conclusions that I have is that each and every ABI like Web3.Utils.st20ABI and the rest must be precisely the version of ABI that matches the original standard without any deviation.
Since we allow developers to reference these ABIs from our project it's our responsibility to:

  • validate the ABI (it must match the standard);
  • give the link to the place where we got this ABI from;
  • recommend developers to use their own ABIs if that's possible as they will have more control over that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yaroslavyaroslav @janndriessen Here is the fix for these commented out lines - #704

The branch with the fix is not based on the one in the current PR so I'll be able to update tests only when the fix is merged into develop.

Copy link
Collaborator

@janndriessen janndriessen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one additional comment. Indeed the work load for mocking looks manageable. Thanks for introducing/working on this.

@@ -60,7 +57,7 @@ public class Web3 {
// FIXME: Rewrite this to CodableTransaction
public class Personal {
var provider: Web3Provider
// weak var web3: web3?
// FIXME: web3 must be weak
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this will be fixed in another PR?

Copy link
Collaborator Author

@JeneaVranceanu JeneaVranceanu Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Just made it more obvious that it needs fixing instead of a commented line of code that tells nothing.
I'll actually update the part after FIXME. Ideally we should remove web3 reference from here completely.

@JeneaVranceanu
Copy link
Collaborator Author

JeneaVranceanu commented Dec 8, 2022

@yaroslavyaroslav @janndriessen Thanks for taking the time to look at this PR.
I'll push a few minor updates in 10 minutes based on your comments.

Copy link
Collaborator

@janndriessen janndriessen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌟 ✅

@yaroslavyaroslav yaroslavyaroslav merged commit d036483 into web3swift-team:fix/contract-operations Dec 9, 2022
@JeneaVranceanu JeneaVranceanu deleted the feat/remote-tests-mocking branch December 9, 2022 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants