Skip to content

Commit

Permalink
🔒 Remove the multicall_value_self Function (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcaversaccio authored Oct 13, 2023
1 parent 5f13de7 commit 55af6d8
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 342 deletions.
145 changes: 71 additions & 74 deletions .gas-snapshot

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# 🕓 Changelog

## [`0.0.4`](https://github.com/pcaversaccio/snekmate/releases/tag/v0.0.4) (Unreleased)
## [`0.0.4`](https://github.com/pcaversaccio/snekmate/releases/tag/v0.0.4) (13-10-2023)

### 🔒 Security Fixes

- **Utility Functions**
- [`Multicall`](https://github.com/pcaversaccio/snekmate/blob/v0.0.4/src/utils/Multicall.vy): Remove the `multicall_value_self` function as the `msg.value` should not be trusted. ([#167](https://github.com/pcaversaccio/snekmate/pull/167))

### 👀 Full Changelog

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "snekmate",
"version": "0.0.4-rc.2",
"version": "0.0.4",
"description": "State-of-the-art, highly opinionated, hyper-optimised, and secure 🐍Vyper smart contract building blocks.",
"author": "Pascal Marco Caversaccio <[email protected]>",
"license": "AGPL-3.0-only",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "snekmate"
version = "0.0.4rc2"
version = "0.0.4"
description = "State-of-the-art, highly opinionated, hyper-optimised, and secure 🐍Vyper smart contract building blocks."
readme = {file = "README.md", content-type = "text/markdown"}
requires-python = ">=3.10"
Expand Down
51 changes: 1 addition & 50 deletions src/utils/Multicall.vy
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
https://github.com/pcaversaccio/snekmate/discussions/82.
The implementation is inspired by Matt Solomon's implementation here:
https://github.com/mds1/multicall/blob/main/src/Multicall3.sol.
@custom:security Make sure you understand how `msg.sender` works in `CALL` vs
@custom:security Make sure you understand how `msg.sender` works in `CALL` vs.
`DELEGATECALL` to the multicall contract, as well as the risks
of using `msg.value` in a multicall. To learn more about the latter, see:
- https://github.com/runtimeverification/verified-smart-contracts/wiki/List-of-Security-Vulnerabilities#payable-multicall,
Expand Down Expand Up @@ -42,14 +42,6 @@ struct BatchSelf:
call_data: Bytes[max_value(uint16)]


# @dev Batch struct for `payable` function calls using this contract
# as destination address.
struct BatchValueSelf:
allow_failure: bool
value: uint256
call_data: Bytes[max_value(uint16)]


# @dev Result struct for function call results.
struct Result:
success: bool
Expand Down Expand Up @@ -164,47 +156,6 @@ def multicall_self(data: DynArray[BatchSelf, max_value(uint8)]) -> DynArray[Resu
return results


@external
@payable
def multicall_value_self(data: DynArray[BatchValueSelf, max_value(uint8)]) -> DynArray[Result, max_value(uint8)]:
"""
@dev Aggregates function calls with a `msg.value` using
`DELEGATECALL`, ensuring that each function returns
successfully if required. Since this function uses
`DELEGATECALL`, the `msg.sender` remains the same
account that invoked the function `multicall_value_self`
in the first place.
@notice Developers can include this function in their own
contract so that users can submit multiple function
calls in one transaction. Since the `msg.sender` is
preserved, it's equivalent to sending multiple transactions
from an EOA (externally-owned account, i.e. non-contract account).
Furthermore, it is important to note that an external
call via `raw_call` does not perform an external code
size check on the target address.
@param data The array of `BatchValueSelf` structs.
@return DynArray The array of `Result` structs.
"""
value_accumulator: uint256 = empty(uint256)
results: DynArray[Result, max_value(uint8)] = []
return_data: Bytes[max_value(uint8)] = b""
success: bool = empty(bool)
for batch in data:
msg_value: uint256 = batch.value
value_accumulator = unsafe_add(value_accumulator, msg_value)
if (batch.allow_failure == False):
return_data = raw_call(self, batch.call_data, max_outsize=255, value=msg_value, is_delegate_call=True)
success = True
results.append(Result({success: success, return_data: return_data}))
else:
success, return_data = \
raw_call(self, batch.call_data, max_outsize=255, value=msg_value, is_delegate_call=True, revert_on_failure=False)
results.append(Result({success: success, return_data: return_data}))
assert msg.value == value_accumulator, "Multicall: value mismatch"
return results


@external
@view
def multistaticcall(data: DynArray[Batch, max_value(uint8)]) -> DynArray[Result, max_value(uint8)]:
Expand Down
204 changes: 0 additions & 204 deletions test/utils/Multicall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -302,210 +302,6 @@ contract MulticallTest is Test {
multicall.multicall_self(batchSelf);
}

function testMulticallValueSelfSuccess() public {
IMulticall.BatchValue[] memory batchValue = new IMulticall.BatchValue[](
4
);
batchValue[0] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("getBlockHash(uint256)", block.number)
);
batchValue[1] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("store(uint256)", type(uint256).max)
);
batchValue[2] = IMulticall.BatchValue(
mockCalleeAddr,
true,
0,
abi.encodeWithSignature("thisMethodReverts()")
);
batchValue[3] = IMulticall.BatchValue(
mockCalleeAddr,
false,
1,
abi.encodeWithSignature("transferEther(address)", etherReceiverAddr)
);

IMulticall.Batch[] memory batch = new IMulticall.Batch[](2);
batch[0] = IMulticall.Batch(
mockCalleeAddr,
false,
abi.encodeWithSignature("getBlockHash(uint256)", block.number)
);
batch[1] = IMulticall.Batch(
mockCalleeAddr,
true,
abi.encodeWithSignature("thisMethodReverts()")
);

IMulticall.BatchValueSelf[]
memory batchValueSelf = new IMulticall.BatchValueSelf[](2);
batchValueSelf[0] = IMulticall.BatchValueSelf(
false,
1,
abi.encodeWithSignature(
"multicall_value((address,bool,uint256,bytes)[])",
batchValue
)
);
batchValueSelf[1] = IMulticall.BatchValueSelf(
true,
0,
abi.encodeWithSignature(
"multistaticcall((address,bool,bytes)[])",
batch
)
);

IMulticall.Result[] memory results = multicall.multicall_value_self{
value: 1
}(batchValueSelf);
assertTrue(results[0].success);
assertTrue(!results[1].success);
assertEq(etherReceiverAddr.balance, 1 wei);
}

function testMulticallValueSelfCase1() public {
IMulticall.BatchValue[] memory batchValue = new IMulticall.BatchValue[](
4
);
batchValue[0] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("getBlockHash(uint256)", block.number)
);
batchValue[1] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("store(uint256)", type(uint256).max)
);
/**
* @dev We don't allow for a failure.
*/
batchValue[2] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("thisMethodReverts()")
);
batchValue[3] = IMulticall.BatchValue(
mockCalleeAddr,
false,
1,
abi.encodeWithSignature("transferEther(address)", etherReceiverAddr)
);

IMulticall.Batch[] memory batch = new IMulticall.Batch[](2);
batch[0] = IMulticall.Batch(
mockCalleeAddr,
false,
abi.encodeWithSignature("getBlockHash(uint256)", block.number)
);
batch[1] = IMulticall.Batch(
mockCalleeAddr,
true,
abi.encodeWithSignature("thisMethodReverts()")
);

IMulticall.BatchValueSelf[]
memory batchValueSelf = new IMulticall.BatchValueSelf[](2);
batchValueSelf[0] = IMulticall.BatchValueSelf(
false,
1,
abi.encodeWithSignature(
"multicall_value((address,bool,uint256,bytes)[])",
batchValue
)
);
batchValueSelf[1] = IMulticall.BatchValueSelf(
true,
0,
abi.encodeWithSignature(
"multistaticcall((address,bool,bytes)[])",
batch
)
);

vm.expectRevert(
abi.encodeWithSelector(Reverted.selector, mockCalleeAddr)
);
multicall.multicall_value_self{value: 1}(batchValueSelf);
}

function testMulticallValueSelfCase2() public {
IMulticall.BatchValue[] memory batchValue = new IMulticall.BatchValue[](
4
);
batchValue[0] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("getBlockHash(uint256)", block.number)
);
batchValue[1] = IMulticall.BatchValue(
mockCalleeAddr,
false,
0,
abi.encodeWithSignature("store(uint256)", type(uint256).max)
);
batchValue[2] = IMulticall.BatchValue(
mockCalleeAddr,
true,
0,
abi.encodeWithSignature("thisMethodReverts()")
);
batchValue[3] = IMulticall.BatchValue(
mockCalleeAddr,
false,
1,
abi.encodeWithSignature("transferEther(address)", etherReceiverAddr)
);

IMulticall.Batch[] memory batch = new IMulticall.Batch[](2);
batch[0] = IMulticall.Batch(
mockCalleeAddr,
false,
abi.encodeWithSignature("getBlockHash(uint256)", block.number)
);
batch[1] = IMulticall.Batch(
mockCalleeAddr,
true,
abi.encodeWithSignature("thisMethodReverts()")
);

IMulticall.BatchValueSelf[]
memory batchValueSelf = new IMulticall.BatchValueSelf[](2);
batchValueSelf[0] = IMulticall.BatchValueSelf(
false,
1,
abi.encodeWithSignature(
"multicall_value((address,bool,uint256,bytes)[])",
batchValue
)
);
batchValueSelf[1] = IMulticall.BatchValueSelf(
true,
0,
abi.encodeWithSignature(
"multistaticcall((address,bool,bytes)[])",
batch
)
);

vm.expectRevert(bytes("Multicall: value mismatch"));
/**
* @dev We send too much `msg.value`.
*/
multicall.multicall_value_self{value: 2}(batchValueSelf);
}

function testMultistaticcallSuccess() public {
IMulticall.Batch[] memory batch = new IMulticall.Batch[](2);
batch[0] = IMulticall.Batch(
Expand Down
10 changes: 0 additions & 10 deletions test/utils/interfaces/IMulticall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ interface IMulticall {
bytes callData;
}

struct BatchValueSelf {
bool allowFailure;
uint256 value;
bytes callData;
}

struct Result {
bool success;
bytes returnData;
Expand All @@ -43,10 +37,6 @@ interface IMulticall {
BatchSelf[] calldata batchSelf
) external returns (Result[] memory results);

function multicall_value_self(
BatchValueSelf[] calldata batchValueSelf
) external payable returns (Result[] memory results);

function multistaticcall(
Batch[] calldata batch
) external pure returns (Result[] memory results);
Expand Down

0 comments on commit 55af6d8

Please sign in to comment.