Skip to content

Commit

Permalink
Address #307
Browse files Browse the repository at this point in the history
  • Loading branch information
0xBugsy committed Sep 7, 2023
1 parent 1a2c244 commit 03c829b
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 16 deletions.
53 changes: 41 additions & 12 deletions src/ulysses-omnichain/VirtualAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {ERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/utils/ERC11
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

import {IVirtualAccount, Call} from "./interfaces/IVirtualAccount.sol";
import {IVirtualAccount, Call, PayableCall} from "./interfaces/IVirtualAccount.sol";
import {IRootPort} from "./interfaces/IRootPort.sol";

/// @title VirtualAccount - Contract for managing a virtual user account on the Root Chain
Expand Down Expand Up @@ -54,22 +54,51 @@ contract VirtualAccount is IVirtualAccount, ERC1155Receiver {
}

/// @inheritdoc IVirtualAccount
function call(Call[] calldata calls)
external
override
requiresApprovedCaller
returns (uint256 blockNumber, bytes[] memory returnData)
{
blockNumber = block.number;
returnData = new bytes[](calls.length);
for (uint256 i = 0; i < calls.length; i++) {
function call(Call[] calldata calls) external override requiresApprovedCaller returns (bytes[] memory returnData) {
uint256 length = calls.length;
returnData = new bytes[](length);

for (uint256 i = 0; i < length;) {
bool success;
Call calldata _call = calls[i];
if (isContract(_call.target)) {
(success, returnData[i]) = _call.target.call(_call.callData);

if (isContract(_call.target)) (success, returnData[i]) = _call.target.call(_call.callData);

if (!success) revert CallFailed();

unchecked {
++i;
}
}
}

/// @inheritdoc IVirtualAccount
function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {
uint256 valAccumulator;
uint256 length = calls.length;
returnData = new bytes[](length);
PayableCall calldata _call;
for (uint256 i = 0; i < length;) {
_call = calls[i];
uint256 val = _call.value;
// Humanity will be a Type V Kardashev Civilization before this overflows - andreas
// ~ 10^25 Wei in existence << ~ 10^76 size uint fits in a uint256
unchecked {
valAccumulator += val;
}

bool success;

if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);

if (!success) revert CallFailed();

unchecked {
++i;
}
}
// Finally, make sure the msg.value = SUM(call[0...i].value)
if (msg.value != valAccumulator) revert CallFailed();
}

/*//////////////////////////////////////////////////////////////
Expand Down
24 changes: 20 additions & 4 deletions src/ulysses-omnichain/interfaces/IVirtualAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ pragma solidity ^0.8.0;

import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

/// @notice Interface for the `Multicall2` contract.
/// @notice Call structure based off `Multicall2` contract for aggregating calls.
struct Call {
address target;
bytes callData;
}

/// @notice Payable call structure based off `Multicall3` contract for aggreagating calls with `msg.value`.
struct PayableCall {
address target;
bytes callData;
uint256 value;
}

/**
* @title Virtual Account Contract
* @notice A Virtual Account allows users to manage assets and perform interactions remotely while allowing dApps to keep encapsulated user balance for accounting purposes.
* @dev This contract is based off Maker's `Multicall2` contract, executes a set of `Call` objects if any of the performed calls is invalid the whole batch should revert.
* @dev This contract is based off `Multicall2` and `Multicall3` contract, executes a set of `Call` or `PayableCall` objects if any of the performed calls is invalid the whole batch should revert.
*/
interface IVirtualAccount is IERC721Receiver {
/**
Expand Down Expand Up @@ -48,10 +55,19 @@ interface IVirtualAccount is IERC721Receiver {
function withdrawERC721(address _token, uint256 _tokenId) external;

/**
* @notice
* @notice Aggregate calls ensuring each call is successful. Inspired by `Multicall2` contract.
* @param callInput The call to make.
* @return The return data of the call.
*/
function call(Call[] calldata callInput) external returns (bytes[] memory);

/**
* @notice Aggregate calls with a msg value ensuring each call is successful. Inspired by `Multicall3` contract.
* @param calls The calls to make.
* @return The return data of the calls.
* @dev Reverts if msg.value is less than the sum of the call values.
*/
function call(Call[] calldata callInput) external returns (uint256 blockNumber, bytes[] memory);
function payableCall(PayableCall[] calldata calls) external payable returns (bytes[] memory);

/*///////////////////////////////////////////////////////////////
ERRORS
Expand Down
44 changes: 44 additions & 0 deletions test/ulysses-omnichain/RootTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {RootBridgeAgentFactory} from "@omni/factories/RootBridgeAgentFactory.sol
import {BranchBridgeAgentFactory} from "@omni/factories/BranchBridgeAgentFactory.sol";
import {ArbitrumBranchBridgeAgentFactory} from "@omni/factories/ArbitrumBranchBridgeAgentFactory.sol";

import {VirtualAccount, PayableCall} from "@omni/VirtualAccount.sol";

//UTILS
import {DepositParams, DepositMultipleParams} from "./mocks/MockRootBridgeAgent.t.sol";
import {Deposit, DepositStatus, DepositMultipleInput, DepositInput} from "@omni/interfaces/IBranchBridgeAgent.sol";
Expand Down Expand Up @@ -1414,6 +1416,48 @@ contract RootTest is DSTestPlus {
);
}

function testPayableCall() public {
// Set up
testAddLocalTokenArbitrum();

// Prepare data
PayableCall[] memory calls = new PayableCall[](1);

// Mock Omnichain dApp call
calls[0] = PayableCall({
target: arbitrumWrappedNativeToken,
callData: abi.encodeWithSelector(bytes4(0xd0e30db0)),
value: 1 ether
});

// Prank into MulticallBridgeAgent
hevm.startPrank(address(multicallBridgeAgent));

// Get User Virtual Account
VirtualAccount userAccount = RootPort(address(rootPort)).fetchVirtualAccount(address(this));

// Toggle Router Virtual Account use for tx execution
RootPort(address(rootPort)).toggleVirtualAccountApproved(userAccount, address(rootMulticallRouter));

hevm.stopPrank();

//Prank into MulticallRootRouter
hevm.startPrank(address(rootMulticallRouter));

// Get some gas.
hevm.deal(address(rootMulticallRouter), 1 ether);

// Call Deposit function
userAccount.payableCall{value: 1 ether}(calls);

console2.log("Virtual Account Balance:", MockERC20(arbitrumWrappedNativeToken).balanceOf(address(userAccount)));

require(
MockERC20(arbitrumWrappedNativeToken).balanceOf(address(userAccount)) == 1 ether,
"User should have 1 wrapped token"
);
}

////////////////////////////////////////////////////////////////////////// HELPERS ///////////////////////////////////////////////////////////////////

function testCreateDepositSingle(
Expand Down

0 comments on commit 03c829b

Please sign in to comment.