Skip to content

Commit

Permalink
Merge pull request #3 from akamo778/feature/submission-session-account
Browse files Browse the repository at this point in the history
session account wallet implementation
  • Loading branch information
uF4No authored Nov 7, 2022
2 parents d1eddfc + f57c36c commit 0bb3e64
Show file tree
Hide file tree
Showing 17 changed files with 3,902 additions and 0 deletions.
133 changes: 133 additions & 0 deletions submissions/session-wallet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

artifacts-zk/
cache-zk/
2 changes: 2 additions & 0 deletions submissions/session-wallet/About.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
twitter: https://twitter.com/akamo778
discord: Ryo#2410
71 changes: 71 additions & 0 deletions submissions/session-wallet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Session Wallet
## Overview

In most cases, the wallet must be opened when sending a transaction.

With an app that requires only a few transaction sends, such as DeFi, the operation of opening the wallet is not stressful.
However, if it is an app that sends transactions frequently, such as gaming or social media, it becomes a big problem. You need to open the wallet frequently and cannot fully use the app.

It is the worst UX in the world.

To solve this problem, I created a wallet that can issue session keys.
You give the issued session key to a trusted institution (e.g., a gaming company) to send transactions on your behalf.
In other words, your wallet is temporarily under the control of that institution.
However, you do not need to give them your private key, you just need to give them your session key.
Of course, you can deactivate the session key.

Now you can open your wallet once and focus on your game the rest of the time.

## Deploy contracts

Execute the following command to setup

```
yarn install
yarn workspace contracts setup
yarn workspace contracts hardhat compile
```

Then, set the following environment variables
* INFURA_API_KEY
* DEPLOYER_PRIVATE_KEY
* ACCOUNT_OWNER_ADDRESS

Deploy the AccountFactory contract with the following command

```
yarn workspace contracts deploy:factory
```

The address of the deployed AccountFactory contract will be displayed on your terminal, copy it to ACCOUNT_FACTORY_ADDRESS in the .env file.

Deploy the Account contract with the following command

```
yarn workspace contracts deploy:account
```

The address of the deployed Account contract will be displayed on your terminal, copy it to ACCOUNT_ADDRESS in the .env file.

## Running Sample Scripts

After deployment is complete, you can run the following sample script to create a session and send transactions by the session.

ACCOUNT_OWNER_PRIVATE_KEY and SESSION_OWNER_PRIVATE_KEY must be specified in the .env file

```
yarn workspace contracts hardhat run scripts/create-session.ts
yarn workspace contracts hardhat run scripts/transfer-by-session.ts
```

Finally, delete the session.

```
yarn workspace contracts hardhat run scripts/delete-session.ts
```

If you retransfer by old session, some error will occur.

```
yarn workspace contracts hardhat run scripts/transfer-by-session.ts
```
14 changes: 14 additions & 0 deletions submissions/session-wallet/code/contracts/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
INFURA_API_KEY=

# to deploy contracts
DEPLOYER_PRIVATE_KEY=0x
ACCOUNT_OWNER_ADDRESS=0x
ACCOUNT_DEPLOYMENT_SALT=0x0000000000000000000000000000000000000000000000000000000000000000

# memo deployed contracts
ACCOUNT_FACTORY_ADDRESS=0x
ACCOUNT_ADDRESS=0x

# scripts
ACCOUNT_OWNER_PRIVATE_KEY=0x
SESSION_OWNER_PRIVATE_KEY=0x
136 changes: 136 additions & 0 deletions submissions/session-wallet/code/contracts/contracts/Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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/TransactionHelper.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";
import "@matterlabs/zksync-contracts/l2/system-contracts/SystemContractsCaller.sol";

import "./SocialRecovery.sol";
import "./Session.sol";

contract Account is Ownable, Session, SocialRecovery, IAccount {
using TransactionHelper for Transaction;

bytes4 constant EIP1271_SUCCESS_RETURN_VALUE = 0x1626ba7e;

modifier onlyBootloader() {
require(
msg.sender == BOOTLOADER_FORMAL_ADDRESS,
"Only bootloader can call this method"
);
_;
}

constructor(address owner) {
_transferOwnership(owner);
}

function validateTransaction(
bytes32,
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) external payable override onlyBootloader {
_validateTransaction(_suggestedSignedHash, _transaction);
}

function _validateTransaction(
bytes32 _suggestedSignedHash,
Transaction calldata _transaction
) internal {
SystemContractsCaller.systemCall(
uint32(gasleft()),
address(NONCE_HOLDER_SYSTEM_CONTRACT),
0,
abi.encodeCall(
INonceHolder.incrementMinNonceIfEquals,
(_transaction.reserved[0])
)
);

bytes32 txHash;
if (_suggestedSignedHash == bytes32(0)) {
txHash = _transaction.encodeHash();
} else {
txHash = _suggestedSignedHash;
}


require(_transaction.signature.length == 65, "Signature length is incorrect");

address signer = ECDSA.recover(txHash, _transaction.signature);
address to = address(uint160(_transaction.to));

require(owner() == signer || inSession(signer, to, _transaction.data), "Signature is invalid");
}

function executeTransaction(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable override onlyBootloader {
_executeTransaction(_transaction);
}

function _executeTransaction(Transaction calldata _transaction) internal {
address to = address(uint160(_transaction.to));
uint256 value = _transaction.reserved[1];
bytes memory data = _transaction.data;

if (to == address(DEPLOYER_SYSTEM_CONTRACT)) {
SystemContractsCaller.systemCall(
uint32(gasleft()),
to,
uint128(_transaction.reserved[1]),
_transaction.data
);
} else {
bool success;
assembly {
success := call(
gas(),
to,
value,
add(data, 0x20),
mload(data),
0,
0
)
}
require(success);
}
}

function executeTransactionFromOutside(Transaction calldata _transaction)
external
payable
{
_validateTransaction(bytes32(0), _transaction);

_executeTransaction(_transaction);
}

function payForTransaction(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable override onlyBootloader {
bool success = _transaction.payToTheBootloader();
require(success, "Failed to pay the fee to the operator");
}

function prePaymaster(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable override onlyBootloader {
_transaction.processPaymasterInput();
}

receive() external payable {
assert(msg.sender != BOOTLOADER_FORMAL_ADDRESS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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/SystemContractsCaller.sol";

contract AccountFactory {
bytes32 public aaBytecodeHash;

constructor(bytes32 _aaBytecodeHash) {
aaBytecodeHash = _aaBytecodeHash;
}

function deployAccount(
bytes32 salt,
address owner
) external returns (address accountAddress) {
bytes memory returnData = SystemContractsCaller.systemCall(
uint32(gasleft()),
address(DEPLOYER_SYSTEM_CONTRACT),
0,
abi.encodeCall(
DEPLOYER_SYSTEM_CONTRACT.create2Account,
(salt, aaBytecodeHash, abi.encode(owner))
)
);

(accountAddress, ) = abi.decode(returnData, (address, bytes));
}
}
Loading

0 comments on commit 0bb3e64

Please sign in to comment.