Skip to content
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

test(e2e-evm): Add CallCode tests for ABI compliance #2034

Draft
wants to merge 11 commits into
base: dl-e2e-abi-tests-refactor
Choose a base branch
from

Conversation

drklee3
Copy link
Member

@drklee3 drklee3 commented Oct 16, 2024

Description

  • Add functionCallCode to low level caller contract
    • Document details of the inline assembly
  • Test specific callcode behavior to verify functionCallCode
    • Verify expected msg.sender, msg.value, and storage location
  • Add callcode use for ABI basic tests to call mock contracts.

Initial set of tests that just verify the use of callcode. This is a
prerequisite set of tests that ensure our Caller contract uses callcode
as expected, as the ABI compliance tests do not validate this behavior.
Build the whole tx params in the test cases themselves to be more
explicit and more flexible.
Validate behavior of callcode when storage is used, primarily where the
storage is set.
This ensures value was transferred in the tests, which paired with the
msg.value checks, shows that msg.value is not preserved in callcode.
Expecting 0 value would make the conditional fail with a falsey value.
Adding additional else case to ensure the return value is undefined as
an extra check in case the assertions are not being run.

This correctly validates msg.value does indeed have the value passed to
callcode(), just not the same one as msg.value of the parent caller.
@@ -170,16 +170,29 @@ describe("ABI_BasicTests", function () {
{
name: "can be called by low level contract call",
txParams: (ctx) => ({
to: caller.address,
to: lowLevelCaller.address,
Copy link
Member

Choose a reason for hiding this comment

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

Execellent rename 👍. I was a bit lazy with the initial one -- lowLevelCaller gives better clarity on what this test is doing

//
let publicClient: PublicClient;
let walletClient: WalletClient;
let lowLevelCaller: GetContractReturnType<ArtifactsMap["Caller"]["abi"]>;
Copy link
Member

Choose a reason for hiding this comment

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

Great typing

// callCodeTestCaseStorage is for setStorageValue() call tests to validate
// which contract the storage is set on and the expected storage value
type callCodeTestCaseStorage = callCodeTestCaseBase & {
wantSender?: never;
Copy link
Member

Choose a reason for hiding this comment

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

🥇 nice explicit check here

wantStorageValue: bigint;
};

type callCodeTestCase = callCodeTestCaseSendAndValue | callCodeTestCaseStorage;
Copy link
Member

Choose a reason for hiding this comment

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

This is quite a nice method for adding multiple kinds of tests in the same suite, thanks for teaching me this ✍️

Copy link
Member

Choose a reason for hiding this comment

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

The want* prefix is surprising nice to read

functionName: "functionCall",
args: [ctx.address, funcSelector],
}),
gas: contractCallerGas,
}),
expectedStatus: "success",
},
{
Copy link
Member

Choose a reason for hiding this comment

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

🔥

txData.account = whaleAddress;

if (!txData.to) {
expect.fail("to field not set");
Copy link
Member

Choose a reason for hiding this comment

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

Nice type assertions to guard against errors in the test cases

// Storage tests if applicable
if (tc.wantStorageContract) {
const storageContract = tc.wantStorageContract(ctx);
const storageValue = await publicClient.getStorageAt({
Copy link
Member

Choose a reason for hiding this comment

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

Good use of the storage API when the calling contract doesn't have a function to retrieve the value via call

@@ -18,12 +18,67 @@ contract Caller {

// solhint-disable-next-line no-inline-assembly
assembly {
// Bubble up errors: revert(pointer, length of revert reason)
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for adding comments where I should have left them 🥇

function functionCallCode(address to, bytes calldata data) external payable {
// solhint-disable-next-line no-inline-assembly
assembly {
// Copy the calldata to memory, as callcode uses memory pointers.
Copy link
Member

Choose a reason for hiding this comment

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

💯 and good note on memory safety for future reference if modifying this function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants