Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
pragma solidity ^0.5.0;

/* Library Imports */
import { DataTypes } from "../utils/libraries/DataTypes.sol";
import { TransactionParser } from "../utils/libraries/TransactionParser.sol";
import { ECDSAUtils } from "../utils/libraries/ECDSAUtils.sol";
import { ExecutionManagerWrapper } from "../utils/libraries/ExecutionManagerWrapper.sol";
import { RLPReader } from "../utils/libraries/RLPReader.sol";

/* Contract Imports */
import { ExecutionManager } from "../ovm/ExecutionManager.sol";

/**
* @title ECDSAContractAccount
* @dev NOTE: This contract must be made upgradeable!
*/
contract ECDSAContractAccount {
Copy link
Contributor

Choose a reason for hiding this comment

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

Right now this EOA contract is not upgradable but it definitely should be & also shouldn't be hard to make it. This contract won't have to change too much but we just need to make a more generalized: EOAContract which starts out with it's implementationAddress pointing at this contract, as well as a simple function

So it's important that we allow upgradability

should add a very simple function to this in order to enable upgradability --

    function upgrade(address newImplementationAddress) {
        ExecutionManger em = ExecutionManager(msg.sender);
        require(em.ovmCALLER() == em.ovmADDRESS());
        implementationAddress = newImplementationAddress;
    }

This can be the final form of the EOAContract

Copy link
Contributor

Choose a reason for hiding this comment

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

So the parent / upgradable contract would look something like:

contract EOA {
    address implementationAddress;
    function() public {
        ExecutionManger em = ExecutionManager(msg.sender)
        address target;
        bytes memory returndata;
        if (implementationAddress == address(0)) {
            (target, returndata) = em.ovmCALL(ECDSAContractAccount_ADDRESS, calldata) // call the ECDSAContractAccount
        } else {
            (target, returndata) = em.ovmCALL(implementationAddress, calldata)  // call the alt implementation
        }
        em.ovmCALL(target, returndata)
    }
    function upgrade() {
        ExecutionManger em = ExecutionManager(msg.sender);
        require(em.ovmCALLER() == em.ovmADDRESS());
        implementationAddress = newImplementationAddress;
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! I'll add this.

/*
* Constructor
*/

constructor()
public
{
// TODO: Pay the Sequencer a fee in the ETH ERC-20 token.
}


/*
* Public Functions
*/

/**
* Executes a signed transaction.
* @param _transaction Signed EOA transaction.
* @param _isEthSignedMessage Whether or not the user used the `Ethereum Signed Message` prefix.
* @param _v Signature `v` parameter.
* @param _r Signature `r` parameter.
* @param _s Signature `s` parameter.
* @return Result of executing the transaction.
*/
function execute(
bytes memory _transaction,
bool _isEthSignedMessage,
uint8 _v,
bytes32 _r,
bytes32 _s
)
public
returns (
bytes memory _ret
)
{
DataTypes.EOATransaction memory decodedTx = TransactionParser.decodeEOATransaction(
_transaction
);
bytes memory encodedTx = TransactionParser.encodeEOATransaction(
decodedTx,
_isEthSignedMessage
);

ExecutionManager executionManager = ExecutionManager(msg.sender);

require(
ECDSAUtils.recover(
encodedTx,
_isEthSignedMessage,
_v,
_r,
_s,
ExecutionManagerWrapper.ovmCHAINID(address(executionManager))
) == ExecutionManagerWrapper.ovmADDRESS(address(executionManager)),
"Provided signature is invalid."
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Ugh one horrible note is that if we transpile revert (which we have to) we will need to transpile this contract so that we don't actually REVERT when this require fails. 😭

I guess this is an argument for not adding upgradability until we have the final version of the contracts because we'll have to change a bunch of stuff when we rewrite these contracts anyway


uint256 expectedNonce = executionManager.ovmGETNONCE() + 1;
require(
decodedTx.nonce == expectedNonce,
"Nonce must match expected nonce."
);
executionManager.ovmSETNONCE(expectedNonce);

if (decodedTx.to == address(0)) {
_ret = abi.encode(
ExecutionManagerWrapper.ovmCREATE(
address(executionManager),
decodedTx.data,
decodedTx.gasLimit
)
);
} else {
_ret = ExecutionManagerWrapper.ovmCALL(
address(executionManager),
decodedTx.to,
decodedTx.data,
decodedTx.gasLimit
);
}

return _ret;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I just realized that this does not include fee logic! We'll need to add an ERC20 contract precompile which is at a fixed address & which we use for collecting fees inside of the EOA account.

Again, either we add it here / include in this PR or we'll need to make another ticket for it

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import { L1MessageSender } from "./precompiles/L1MessageSender.sol";
import { StateManager } from "./StateManager.sol";
import { SafetyChecker } from "./SafetyChecker.sol";
import { StateManagerGasSanitizer } from "./StateManagerGasSanitizer.sol";
import { ECDSAContractAccount } from "../accounts/ECDSAContractAccount.sol";

/* Library Imports */
import { ContractResolver } from "../utils/resolvers/ContractResolver.sol";
import { DataTypes } from "../utils/libraries/DataTypes.sol";
import { ContractAddressGenerator } from "../utils/libraries/ContractAddressGenerator.sol";
import { RLPWriter } from "../utils/libraries/RLPWriter.sol";
import { EthUtils } from "../utils/libraries/EthUtils.sol";

/* Testing Imports */
import { StubSafetyChecker } from "./test-helpers/StubSafetyChecker.sol";
import { console } from "@nomiclabs/buidler/console.sol";

/**
* @title ExecutionManager
Expand Down Expand Up @@ -151,65 +151,9 @@ contract ExecutionManager is ContractResolver {
}


/*********************
* Execute EOA Calls *
*********************/

/**
* Execute an Externally Owned Account (EOA) call. This will accept all information required
* for an OVM transaction as well as a signature from an EOA. First we will calculate the
* sender address (EOA address) and then we will perform the call.
* @param _timestamp The timestamp which should be used for this call's context.
* @param _queueOrigin The parent-chain queue from which this call originated.
* @param _nonce The current nonce of the EOA.
* @param _ovmEntrypoint The contract which this transaction should be executed against.
* @param _callBytes The calldata for this ovm transaction.
* @param _v The v value of the ECDSA signature + CHAIN_ID.
* @param _r The r value of the ECDSA signature.
* @param _s The s value of the ECDSA signature.
*/
function executeEOACall(
uint _timestamp,
uint _queueOrigin,
uint _nonce,
address _ovmEntrypoint,
bytes memory _callBytes,
uint _ovmTxGasLimit,
uint8 _v,
bytes32 _r,
bytes32 _s
)
public
{
StateManager stateManager = resolveStateManager();

// Get EOA address
address eoaAddress = recoverEOAAddress(_nonce, _ovmEntrypoint, _callBytes, _v, _r, _s);

// Require that the EOA signature isn't zero (invalid signature)
require(eoaAddress != ZERO_ADDRESS, "Failed to recover signature");

// Require nonce to be correct
require(_nonce == stateManager.getOvmContractNonce(eoaAddress), "Incorrect nonce!");

emit CallingWithEOA(
eoaAddress,
_ovmEntrypoint
);

// Make the EOA call for the account
executeTransaction(
_timestamp,
0, // note: since executeEOACall is soon to be deprecated, not bothering to add blockNumber here.
_queueOrigin,
_ovmEntrypoint,
_callBytes,
eoaAddress,
ZERO_ADDRESS,
_ovmTxGasLimit,
false
);
}
/*************************
* Transaction Execution *
*************************/

/**
* Execute a transaction. Note that unsigned EOA calls are unauthenticated.
Expand Down Expand Up @@ -321,6 +265,9 @@ contract ExecutionManager is ContractResolver {
// set the new cumulative gas
updateCumulativeGas(gasConsumedByExecution);

// Reset the active contract.
switchActiveContract(address(0));

assembly {
let resultData := add(result, 0x20)
if eq(success, 1) {
Expand All @@ -341,58 +288,73 @@ contract ExecutionManager is ContractResolver {
}
}

/**
* Recover the EOA of an ECDSA-signed Ethereum transaction. Note some values will be set to
* zero by default. Additionally, the `to=ZERO_ADDRESS` is reserved for contract creation
* transactions.
* @param _nonce The nonce of the transaction.
* @param _to The entrypoint / recipient of the transaction.
* @param _callData The calldata which will be applied to the entrypoint contract.
* @param _v The v value of the ECDSA signature + CHAIN_ID.
* @param _r The r value of the ECDSA signature.
* @param _s The s value of the ECDSA signature.
*/
function recoverEOAAddress(
uint _nonce,
address _to,
bytes memory _callData,

/*******************
* OVM EOA Opcodes *
*******************/

function ovmCREATEEOA(
bytes32 _messageHash,
uint8 _v,
bytes32 _r,
bytes32 _s
)
public
view
returns (address)
returns (
address
)
{
bytes[] memory message = new bytes[](9);
message[0] = RLPWriter.encodeUint(_nonce); // Nonce
message[1] = RLPWriter.encodeUint(0); // Gas price
message[2] = RLPWriter.encodeUint(gasMeterConfig.OvmTxMaxGas); // Gas limit

// To -- Special rlp encoding handling if _to is the ZERO_ADDRESS
if (_to == ZERO_ADDRESS) {
message[3] = RLPWriter.encodeUint(0);
} else {
message[3] = RLPWriter.encodeAddress(_to);
}
address eoa = ecrecover(
_messageHash,
(_v - uint8(executionContext.chainId) * 2) - 8,
_r,
_s
);

require(
eoa != address(0),
"Provided signature is invalid."
);

StateManager stateManager = resolveStateManager();
require(
stateManager.getCodeContractAddressFromOvmAddress(eoa) == address(0),
"EOA account has already been created."
);

ECDSAContractAccount eoaContractAccount = new ECDSAContractAccount();
stateManager.associateCodeContract(eoa, address(eoaContractAccount));

return eoa;
}

message[4] = RLPWriter.encodeUint(0); // Value
message[5] = RLPWriter.encodeBytes(_callData); // Data
message[6] = RLPWriter.encodeUint(executionContext.chainId); // ChainID
message[7] = RLPWriter.encodeUint(0); // Zeros for R
message[8] = RLPWriter.encodeUint(0); // Zeros for S

bytes memory encodedMessage = RLPWriter.encodeList(message);
bytes32 hash = keccak256(abi.encodePacked(encodedMessage));

/*
* Replay protection is used to prevent signatures on one chain from
* being used on other chains. To support replay protection ethereum
* modifies the value of v in the signature to be different for each
* chainID. This was implemented based on the following EIP:
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification
*/
return ecrecover(hash, (_v - uint8(executionContext.chainId) * 2) - 8, _r, _s);
function ovmSETNONCE(
uint256 _nonce
)
public
{
require(
executionContext.ovmActiveContract != address(0),
"Must be inside a valid execution context."
);

require(
_nonce > ovmGETNONCE(),
"New nonce must be greater than the current nonce."
);

StateManager stateManager = resolveStateManager();
stateManager.setOvmContractNonce(executionContext.ovmActiveContract, _nonce);
}

function ovmGETNONCE()
public
returns (
uint256
)
{
StateManager stateManager = resolveStateManager();
return stateManager.getOvmContractNonce(executionContext.ovmActiveContract);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ contract FullStateManager is StateManager {

mapping(address => mapping(bytes32 => bytes32)) private ovmContractStorage;
mapping(address => uint) private ovmContractNonces;
mapping(address => address) private ovmAddressToCodeContractAddress;
mapping(address => address) private codeContractAddressToOvmAddress;
mapping(address => address) public ovmAddressToCodeContractAddress;
mapping(address => address) public codeContractAddressToOvmAddress;


/*
Expand Down
Loading