-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
405 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,239 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IAccount.sol"; | ||
import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; | ||
import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractHelper.sol"; | ||
import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol"; | ||
import {BOOTLOADER_FORMAL_ADDRESS, NONCE_HOLDER_SYSTEM_CONTRACT, DEPLOYER_SYSTEM_CONTRACT, INonceHolder} from "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; | ||
|
||
|
||
contract PensionAccount is IAccount { | ||
// to get transaction hash | ||
using TransactionHelper for *; | ||
|
||
// Owner of the account | ||
address public owner; | ||
|
||
// Addresses for DEX and tokens | ||
address public dex; | ||
address public DOGE; | ||
address public PEPE; | ||
address public SHIB; | ||
address public BTC; | ||
|
||
// Expiry timestamp | ||
uint256 public expiryTimestamp; | ||
|
||
// Event for swap action | ||
event Swap(address indexed token, uint256 amountToSwap); | ||
|
||
constructor(address _owner, address _dex, address _doge, address _pepe, address _shib, address _btc) { | ||
owner = _owner; | ||
dex = _dex; | ||
DOGE = _doge; | ||
PEPE = _pepe; | ||
SHIB = _shib; | ||
BTC = _btc; | ||
|
||
// Set the expiry timestamp to 3 minutes from now | ||
expiryTimestamp = block.timestamp 3 minutes; | ||
} | ||
|
||
// Modifier to check if the time lock has expired | ||
modifier afterExpiry() { | ||
require(block.timestamp >= expiryTimestamp, "Action locked until expiry time"); | ||
_; | ||
} | ||
|
||
receive() external payable { | ||
uint256 amountToSwap = msg.value / 4; | ||
emit Swap(DOGE, amountToSwap); | ||
emit Swap(PEPE, amountToSwap); | ||
emit Swap(SHIB, amountToSwap); | ||
emit Swap(BTC, amountToSwap); | ||
} | ||
|
||
|
||
// Override the executeTransaction function to include the time lock | ||
function executeTransaction( | ||
bytes32, // _txHash | ||
bytes32, // _suggestedSignedHash | ||
Transaction calldata _transaction | ||
) external payable ignoreNonBootloader ignoreInDelegateCall afterExpiry { | ||
_execute(_transaction); | ||
} | ||
|
||
// Override the payForTransaction function to include the time lock | ||
function payForTransaction( | ||
bytes32, // _txHash | ||
bytes32, // _suggestedSignedHash | ||
Transaction calldata _transaction | ||
) external payable ignoreNonBootloader ignoreInDelegateCall afterExpiry { | ||
bool success = _transaction.payToTheBootloader(); | ||
require(success, "Failed to pay the fee to the operator"); | ||
} | ||
|
||
// Override the prepareForPaymaster function to include the time lock | ||
function prepareForPaymaster( | ||
bytes32, // _txHash | ||
bytes32, // _suggestedSignedHash | ||
Transaction calldata _transaction | ||
) external payable ignoreNonBootloader ignoreInDelegateCall afterExpiry { | ||
_transaction.processPaymasterInput(); | ||
} | ||
|
||
// ================================================================================================================= | ||
// START DEFAULT ACCOUNT CODE | ||
// ================================================================================================================= | ||
|
||
/** | ||
* @dev Simulate the behavior of the EOA if the caller is not the bootloader. | ||
* Essentially, for all non-bootloader callers halt the execution with empty return data. | ||
* If all functions will use this modifier AND the contract will implement an empty payable fallback() | ||
* then the contract will be indistinguishable from the EOA when called. | ||
*/ | ||
modifier ignoreNonBootloader() { | ||
if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) { | ||
// If function was called outside of the bootloader, behave like an EOA. | ||
assembly { | ||
return(0, 0) | ||
} | ||
} | ||
// Continue execution if called from the bootloader. | ||
_; | ||
} | ||
|
||
/** | ||
* @dev Simulate the behavior of the EOA if it is called via `delegatecall`. | ||
* Thus, the default account on a delegate call behaves the same as EOA on Ethereum. | ||
* If all functions will use this modifier AND the contract will implement an empty payable fallback() | ||
* then the contract will be indistinguishable from the EOA when called. | ||
*/ | ||
modifier ignoreInDelegateCall() { | ||
address codeAddress = SystemContractHelper.getCodeAddress(); | ||
if (codeAddress != address(this)) { | ||
// If the function was delegate called, behave like an EOA. | ||
assembly { | ||
return(0, 0) | ||
} | ||
} | ||
|
||
// Continue execution if not delegate called. | ||
_; | ||
} | ||
|
||
/// @notice Validates the transaction & increments nonce. | ||
/// @dev The transaction is considered accepted by the account if | ||
/// the call to this function by the bootloader does not revert | ||
/// and the nonce has been set as used. | ||
/// @param _suggestedSignedHash The suggested hash of the transaction to be signed by the user. | ||
/// This is the hash that is signed by the EOA by default. | ||
/// @param _transaction The transaction structure itself. | ||
/// @dev Besides the params above, it also accepts unused first paramter "_txHash", which | ||
/// is the unique (canonical) hash of the transaction. | ||
function validateTransaction( | ||
bytes32, // _txHash | ||
bytes32 _suggestedSignedHash, | ||
Transaction calldata _transaction | ||
) external payable override ignoreNonBootloader ignoreInDelegateCall returns (bytes4 magic) { | ||
magic = _validateTransaction(_suggestedSignedHash, _transaction); | ||
} | ||
|
||
/// @notice Inner method for validating transaction and increasing the nonce | ||
/// @param _suggestedSignedHash The hash of the transaction signed by the EOA | ||
/// @param _transaction The transaction. | ||
function _validateTransaction( | ||
bytes32 _suggestedSignedHash, | ||
Transaction calldata _transaction | ||
) internal returns (bytes4 magic) { | ||
// Note, that nonce holder can only be called with "isSystem" flag. | ||
SystemContractsCaller.systemCallWithPropagatedRevert( | ||
uint32(gasleft()), | ||
address(NONCE_HOLDER_SYSTEM_CONTRACT), | ||
0, | ||
abi.encodeCall(INonceHolder.incrementMinNonceIfEquals, (_transaction.nonce)) | ||
); | ||
|
||
// Even though for the transaction types present in the system right now, | ||
// we always provide the suggested signed hash, this should not be | ||
// always expected. In case the bootloader has no clue what the default hash | ||
// is, the bytes32(0) will be supplied. | ||
bytes32 txHash = _suggestedSignedHash != bytes32(0) ? _suggestedSignedHash : _transaction.encodeHash(); | ||
|
||
// The fact there is are enough balance for the account | ||
// should be checked explicitly to prevent user paying for fee for a | ||
// transaction that wouldn't be included on Ethereum. | ||
uint256 totalRequiredBalance = _transaction.totalRequiredBalance(); | ||
require(totalRequiredBalance <= address(this).balance, "Not enough balance for fee value"); | ||
|
||
if (_isValidSignature(txHash, _transaction.signature)) { | ||
magic = ACCOUNT_VALIDATION_SUCCESS_MAGIC; | ||
} | ||
} | ||
|
||
/// @notice Method that should be used to initiate a transaction from this account by an external call. | ||
/// @dev The custom account is supposed to implement this method to initiate a transaction on behalf | ||
/// of the account via L1 -> L2 communication. However, the default account can initiate a transaction | ||
/// from L1, so we formally implement the interface method, but it doesn't execute any logic. | ||
/// @param _transaction The transaction to execute. | ||
function executeTransactionFromOutside(Transaction calldata _transaction) external payable override { | ||
// Behave the same as for fallback/receive, just execute nothing, returns nothing | ||
} | ||
|
||
/// @notice Inner method for executing a transaction. | ||
/// @param _transaction The transaction to execute. | ||
function _execute(Transaction calldata _transaction) internal { | ||
address to = address(uint160(_transaction.to)); | ||
uint128 value = Utils.safeCastToU128(_transaction.value); | ||
bytes calldata data = _transaction.data; | ||
uint32 gas = Utils.safeCastToU32(gasleft()); | ||
|
||
// Note, that the deployment method from the deployer contract can only be called with a "systemCall" flag. | ||
bool isSystemCall; | ||
if (to == address(DEPLOYER_SYSTEM_CONTRACT) && data.length >= 4) { | ||
bytes4 selector = bytes4(data[:4]); | ||
// Check that called function is the deployment method, | ||
// the others deployer method is not supposed to be called from the default account. | ||
isSystemCall = | ||
selector == DEPLOYER_SYSTEM_CONTRACT.create.selector || | ||
selector == DEPLOYER_SYSTEM_CONTRACT.create2.selector || | ||
selector == DEPLOYER_SYSTEM_CONTRACT.createAccount.selector || | ||
selector == DEPLOYER_SYSTEM_CONTRACT.create2Account.selector; | ||
} | ||
bool success = EfficientCall.rawCall(gas, to, value, data, isSystemCall); | ||
if (!success) { | ||
EfficientCall.propagateRevert(); | ||
} | ||
} | ||
|
||
/// @notice Validation that the ECDSA signature of the transaction is correct. | ||
/// @param _hash The hash of the transaction to be signed. | ||
/// @param _signature The signature of the transaction. | ||
/// @return EIP1271_SUCCESS_RETURN_VALUE if the signaure is correct. It reverts otherwise. | ||
function _isValidSignature(bytes32 _hash, bytes memory _signature) internal view returns (bool) { | ||
require(_signature.length == 65, "Signature length is incorrect"); | ||
uint8 v; | ||
bytes32 r; | ||
bytes32 s; | ||
assembly { | ||
r := mload(add(_signature, 0x20)) | ||
s := mload(add(_signature, 0x40)) | ||
v := and(mload(add(_signature, 0x41)), 0xff) | ||
} | ||
require(v == 27 || v == 28, "v is neither 27 nor 28"); | ||
|
||
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "Invalid s"); | ||
|
||
address recoveredAddress = ecrecover(_hash, v, r, s); | ||
|
||
return recoveredAddress == owner && recoveredAddress != address(0); | ||
} | ||
|
||
fallback() external payable ignoreInDelegateCall { | ||
// fallback of default account shouldn't be called by bootloader under no circumstances | ||
assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS); | ||
|
||
// If the contract is called directly, behave like an EOA | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,42 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; | ||
import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/SystemContractsCaller.sol"; | ||
|
||
contract PensionAccountFactory { | ||
bytes32 public pensionAccountBytecodeHash; | ||
|
||
constructor(bytes32 _pensionAccountBytecodeHash) { | ||
pensionAccountBytecodeHash = _pensionAccountBytecodeHash; | ||
} | ||
|
||
function deployPensionAccount( | ||
bytes32 salt, | ||
address owner, | ||
address dex, | ||
address doge, | ||
address pepe, | ||
address shib, | ||
address btc | ||
) external returns (address pensionAccountAddress) { | ||
(bool success, bytes memory returnData) = SystemContractsCaller | ||
.systemCallWithReturndata( | ||
uint32(gasleft()), | ||
address(DEPLOYER_SYSTEM_CONTRACT), | ||
uint128(0), | ||
abi.encodeCall( | ||
DEPLOYER_SYSTEM_CONTRACT.create2Account, | ||
( | ||
salt, | ||
pensionAccountBytecodeHash, | ||
abi.encode(owner, dex, doge, pepe, shib, btc), | ||
IContractDeployer.AccountAbstractionVersion.Version1 | ||
) | ||
) | ||
); | ||
require(success, "Deployment failed"); | ||
|
||
(pensionAccountAddress) = abi.decode(returnData, (address)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.