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 message call tests #2034

Draft
wants to merge 19 commits into
base: dl-e2e-abi-tests-refactor
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 93 additions & 4 deletions tests/e2e-evm/contracts/ABI_BasicTests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,105 @@ pragma solidity ^0.8.24;
// Low level caller
//
contract Caller {
/**
* @dev Call a function via CALL with the current msg.value
*/
function functionCall(address payable to, bytes calldata data) external payable {
(bool success, bytes memory result) = to.call{value: msg.value}(data);
this.functionCallWithValue(to, msg.value, data);
}

/**
* @dev Call a function via CALL with a specific value that may be different
* from the current msg.value
*/
function functionCallWithValue(address payable to, uint256 value, bytes calldata data) public payable {
(bool success, bytes memory result) = to.call{value: value}(data);

if (!success) {
// solhint-disable-next-line gas-custom-errors
if (result.length == 0) revert("reverted with no reason");

// 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 🥇

// - result is a dynamic array, so the first 32 bytes is the
// length of the array.
// - add(32, result) skips the length of the array and points to
// the start of the data.
// - mload(result) reads 32 bytes from the memory location,
// which is the length of the revert reason.
revert(add(32, result), mload(result))
}
}
}

// TODO: Callcode
/**
* @dev Call a contract function via CALLCODE with the current msg.value
*/
function functionCallCode(address to, bytes calldata data) external payable {
this.functionCallCodeWithValue(to, msg.value, data);
}

/**
* @dev Call a contract function via CALLCODE with a specific value that may
* be different from the current msg.value
*/
function functionCallCodeWithValue(address to, uint256 value, 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

// offset is where the actual data starts in the calldata. Copy the
// data to memory starting at 0.
// Note: We are taking full control of memory as we do not return
// to high-level Solidity code. This would be not memory safe as it
// may exceed the scratch space in the first 64 bytes from 0.
// This should be safe to still do, similar to the OpenZeppelin
// proxy contract, overwriting the full memory scratch pad at
// target 0 AND never returning to high-level Solidity code.
calldatacopy(0, data.offset, data.length)

// callcode(g, a, v, in, insize, out, outsize)
// returns 0 on error (eg. out of gas) and 1 on success
let result := callcode(
gas(), // gas
to, // to address
value, // value to send
0, // in - pointer to start of input, 0 since we copied the data to 0
data.length, // insize - size of the input
0, // out
0 // outsize - 0 since we don't know the size of the output
)

// Copy the returned data to memory.
// returndatacopy(t, f, s)
// - t: target location in memory
// - f: source location in return data
// - s: size
// Note: Same memory safety notes as above with calldatacopy()
returndatacopy(0, 0, returndatasize())

switch result
// 0 on error
case 0 {
revert(0, returndatasize())
}
// 1 on success
case 1 {
return(0, returndatasize())
}
// Invalid result
default {
revert(0, 0)
}
}
}

function functionDelegateCall(address to, bytes calldata data) external {
/**
* @dev Call a contract function via DELEGATECALL with the current msg.value
* and current msg.sender. DELEGATECALL cannot specify a different
* value.
*/
function functionDelegateCall(address to, bytes calldata data) external payable {
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = to.delegatecall(data);

Expand All @@ -40,7 +122,12 @@ contract Caller {
}
}

function functionStaticCall(address to, bytes calldata data) external view {
/**
* @dev Call a contract function via STATICCALL with the current msg.value
* and current msg.sender.
* @return The result of the static call in bytes.
*/
function functionStaticCall(address to, bytes calldata data) external view returns (bytes memory) {
(bool success, bytes memory result) = to.staticcall(data);

if (!success) {
Expand All @@ -52,6 +139,8 @@ contract Caller {
revert(add(32, result), mload(result))
}
}

return result;
}
}

Expand Down
4 changes: 4 additions & 0 deletions tests/e2e-evm/contracts/ABI_DisabledTests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ contract NoopDisabledMock is ABI_BasicTests.NoopReceivePayableFallback {
function noopPayable() external payable {
mockRevert();
}
/**
* @dev This function is intentionally not marked as pure to test the
* behavior of view functions in disabled contracts.
*/
// solc-ignore-next-line func-mutability
function noopView() external view {
mockRevert();
Expand Down
43 changes: 43 additions & 0 deletions tests/e2e-evm/contracts/ContextInspector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ContextInspector {
/**
* @dev Emitted when the emitMsgSender() function is called.
*/
event MsgSender(address sender);

/**
* @dev Emitted when the emitMsgValue() function is called.
*
* Note that `value` may be zero.
*/
event MsgValue(uint256 value);

function emitMsgSender() external;
function emitMsgValue() external payable;
function getMsgSender() external view returns (address);
}

/**
* @title A contract to inspect the msg context.
* @notice This contract is used to test the expected msg.sender and msg.value
* of a contract call in various scenarios.
*/
contract ContextInspectorMock is ContextInspector {
function emitMsgSender() external {
emit MsgSender(msg.sender);
}

function emitMsgValue() external payable {
emit MsgValue(msg.value);
}

/**
* @dev Returns the current msg.sender. This is primarily used for testing
* staticcall as events are not emitted.
*/
function getMsgSender() external view returns (address) {
return msg.sender;
}
}
19 changes: 19 additions & 0 deletions tests/e2e-evm/contracts/StorageTests.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface StorageBasic {
function setStorageValue(uint256 value) external;
}

/**
* @title A basic contract with a storage value.
* @notice This contract is used to test storage reads and writes, primarily
* for testing storage behavior in delegateCall.
*/
contract StorageBasicMock is StorageBasic {
uint256 public storageValue;

function setStorageValue(uint256 value) external {
storageValue = value;
}
}
Loading
Loading