Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codespace congenial memory 4jw7pxjx44r9cqgrp #67

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
26 changes: 0 additions & 26 deletions Contest.md

This file was deleted.

447 changes: 0 additions & 447 deletions Improvements.rst

This file was deleted.

78 changes: 65 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
# 🔥W5: wallet v5 standard
# W5: wallet smart contract v5

This is an extensible wallet specification aimed at replacing V4 and allowing arbitrary extensions.
New version of wallet smart contract, the previous one was [v4r2](https://github.com/ton-blockchain/wallet-contract).

W5 has **25% lower fees**, supports **gasless transactions** (via third party relayers) and implements a **flexible extension mechanism**.
The entire concept is proposed by the [Tonkeeper team](https://tonkeeper.com/).

New Features:

- Send up to 255 messages at once;

- Signed actions can be sent not only by external message, but also by internal messages (can be used for gasless transactions);

- Unlimited extensions;

- Extension can prohibit signed actions in the wallet (can be used for 2fa or key recovery);

- Optimizations to reduce network fees;

- Better foolproofing safety - reply-protection for external messages, wallet id rethinking;

## Project structure

Expand All @@ -13,25 +27,63 @@ W5 has **25% lower fees**, supports **gasless transactions** (via third party re
- `scripts` - scripts used by the project, mainly the deployment scripts, additionally contains utilities for gas optimisation.
- `fift` - contains standard Fift v0.4.4 library including the assembler and disassembler for gas optimisation utilities.

### Additional documentation

- [Gas improvements](Improvements.rst) - a log of improvements, detailed by primary code paths, global gas counters per commit.
- [Contest](Contest.md) - a note showing some information about interesting improvements during the optimisation contest.

## How to use

### Build

`npm run build:v5`
`npm run build`

### Test

`npm run test`

### Deployment
1. Deploy library: `npm run deploy-library`
2. Deploy wallet: `npm run deploy-wallet`

### Get wallet compiled code
Deploy wallet: `npm run deploy-wallet`

### Known issues

1) Since the `valid_until` is uint32 it will not work after 2106 year. We believe new versions of wallet smart contract will be available by then.

2) If the `action_send_msg` content is invalid and the sendmode has +2, the error will not be ignored. An update of the node is planned where this behaviour will be changed (with +2 sendmode and `action_send_msg` invalid content the error will be ignored).

3) It would be good to do `end_parse()` for messages and contract data. But this is not done within optimisations.

### Gasless flow

1. When sending an USDt (or other Jetton) the user signs one message containing two outgoing USDt transfers:

* USDt transfer to the recipient's address.

* Transfer of a small amount of USDt in favor of the Service.

2. This signed message is sent offchain by HTTPS to the Service backend. The Service backend checks message and sends it to the TON blockchain paying Toncoins for network fees.

### Gasless known issues

1) By requesting a gasless service, a user can have time to increase the seqno on his own, or via another service.

In this case, the gasless service will incur gas costs.

However, this is a non-scalable scenario, as it requires the user to incur gas costs as well.

A blacklist on the service backend side solves the problem.

2) The user can request a gasless service and by means of a specialised extension have time to withdraw the entire balance of Jettons without change seqno.

In this case, the Jetton transfer message from the service will encounter a balance shortage and the Toncoins attached to message will return to the user's wallet.

However, this is a non-scalable scenario, as it requires the user to incur gas costs as well.

A blacklist on the service backend side solves the problem.

### Suggested extensions

1) Decentralised subscriptions. The extension can withdraw a given number of Toncoins or Jettons once in a given period.

2) 2FA: Multisig extension is added, extension prohibits wallet signature;

3) Key recovery: 2FA, but in multisig extension there is an option to change the control keys. Possible cooldown period when the other party can cancel the key change.

4) Key compromise: An extension with a new key is added, extension prohibits wallet signature;

`npm run print-wallet-code`
4 changes: 1 addition & 3 deletions Specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ This is an extensible wallet specification aimed at replacing V4 and allowing ar
* [Features](#features)
* [Overview](#overview)
* [Discussion](#discussion)
* [Wallet ID](#wallet-id)
* [Packed address](#packed-address)
* [TL-B definitions](#tl-b-definitions)
* [Source code](#source-code)

Expand All @@ -30,7 +28,7 @@ Thanks to [Skydev](https://github.com/Skydev0h) for optimization and preparing t
* Extensions can perform the same operations as the signer: emit arbitrary messages on behalf of the owner, add and remove extensions.
* Signed requests can be delivered via internal message to allow 3rd party pay for gas.
* For consistency and ease of indexing, external messages also receive a 32-bit opcode.
* To lay foundation for support of scenarios like 2FA or access recovery it is possible to disable signature authentication.
* To lay foundation for support of scenarios like 2FA or access recovery it is possible to disable signature authentication by extension.

## Overview

Expand Down
Binary file removed contest.png
Binary file not shown.
23 changes: 13 additions & 10 deletions contracts/wallet_v5.fc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ cell verify_c5_actions(cell c5, int is_external) inline {
;; only `action_send_msg` is allowed; `action_set_code`, `action_reserve_currency` or `action_change_library` are not.
cs = cs.enforce_and_remove_action_send_msg_prefix();

throw_unless(error::invalid_c5, cs.slice_bits() == 8); ;; send_mode
throw_unless(error::invalid_c5, cs.slice_bits() == 8); ;; send_mode uint8
throw_unless(error::invalid_c5, cs.slice_refs() == 2); ;; next-action-ref and MessageRelaxed ref

;; enforce that send_mode has +2 bit (ignore errors) set for external message.
Expand Down Expand Up @@ -100,7 +100,7 @@ cell verify_c5_actions(cell c5, int is_external) inline {
return ();
}

;; Loop extended actions until we reach standard actions
;; Loop extended actions
while (true) {
int is_add_extension = cs~check_and_remove_add_extension_prefix();
int is_remove_extension = is_add_extension ? 0 : cs~check_and_remove_remove_extension_prefix();
Expand All @@ -109,7 +109,7 @@ cell verify_c5_actions(cell c5, int is_external) inline {
(int address_wc, int address_hash) = parse_std_addr(cs~load_msg_addr());
(int my_address_wc, _) = parse_std_addr(my_address());

throw_unless(error::extension_wrong_workchain, my_address_wc == address_wc);
throw_unless(error::extension_wrong_workchain, my_address_wc == address_wc); ;; the extension must be in the same workchain as the wallet.

slice data_slice = get_data().begin_parse();
slice data_slice_before_extensions = data_slice~load_bits(size::bool + size::seqno + size::wallet_id + size::public_key);
Expand All @@ -131,7 +131,7 @@ cell verify_c5_actions(cell c5, int is_external) inline {
.store_dict(extensions)
.end_cell());

} elseif (cs~check_and_remove_set_signature_allowed_prefix()) {
} elseif (cs~check_and_remove_set_signature_allowed_prefix()) { ;; allow/disallow signature
throw_unless(error::only_extension_can_change_signature_mode, is_extension);
int allow_signature = cs~load_int(1);
slice data_slice = get_data().begin_parse();
Expand Down Expand Up @@ -184,6 +184,7 @@ cell verify_c5_actions(cell c5, int is_external) inline {
return ();
}
}
;; In case the wallet application has initially, by mistake, deployed a contract with the wrong bit (signature is forbidden and extensions are empty) - we allow such a contract to work.
throw_if(error::signature_disabled, (~ is_signature_allowed) & is_extensions_not_empty);
throw_unless(error::invalid_seqno, seqno == stored_seqno);
throw_unless(error::invalid_wallet_id, wallet_id == stored_wallet_id);
Expand All @@ -193,7 +194,6 @@ cell verify_c5_actions(cell c5, int is_external) inline {
accept_message();
}

;; Store and commit the seqno increment to prevent replays even if the subsequent requests fail.
stored_seqno = stored_seqno + 1;
set_data(begin_cell()
.store_int(true, size::bool) ;; is_signature_allowed
Expand All @@ -202,6 +202,7 @@ cell verify_c5_actions(cell c5, int is_external) inline {
.end_cell());

if (is_external) {
;; For external messages we commit seqno changes, so that even if an exception occurs further on, the reply-protection will still work.
commit();
}

Expand All @@ -217,14 +218,14 @@ cell verify_c5_actions(cell c5, int is_external) inline {

() recv_internal(cell in_msg_full, slice in_msg_body) impure inline {
if (in_msg_body.slice_bits() < size::message_operation_prefix) {
return ();
return (); ;; just receive Toncoins
}
int op = in_msg_body.preload_uint(size::message_operation_prefix);
if ((op != prefix::extension_action) & (op != prefix::signed_internal)) {
return ();
return (); ;; just receive Toncoins
}

;; bounded messages has 0xffffff prefix and skipped by op check
;; bounced messages has 0xffffffff prefix and skipped by op check

if (op == prefix::extension_action) {
in_msg_body~skip_bits(size::message_operation_prefix);
Expand Down Expand Up @@ -257,7 +258,9 @@ cell verify_c5_actions(cell c5, int is_external) inline {

}

;; Additional check to make sure that there are enough bits for reading before signature check
;; Before signature checking we handle errors silently (return), after signature checking we throw exceptions.

;; Check to make sure that there are enough bits for reading before signature check
if (in_msg_body.slice_bits() < size::message_operation_prefix + size::wallet_id + size::valid_until + size::seqno + size::signature) {
return ();
}
Expand Down Expand Up @@ -290,7 +293,7 @@ int get_public_key() method_id {
.preload_uint(size::public_key);
}

;; Returns raw dictionary (or null if empty) where keys are address hashes. Workchains of extensions are same with wallet smart contract workchain
;; Returns raw dictionary (or null if empty) where keys are address hashes. Workchains of extensions are same with wallet smart contract workchain.
cell get_extensions() method_id {
return get_data().begin_parse()
.skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key)
Expand Down
2 changes: 1 addition & 1 deletion scripts/deployLibrary.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { toNano } from 'ton-core';
import { compile, NetworkProvider } from '@ton-community/blueprint';
import { compile, NetworkProvider } from '@ton/blueprint';
import 'dotenv/config';
import { LibraryDeployer } from '../wrappers/library-deployer';

Expand Down
6 changes: 3 additions & 3 deletions scripts/deployWalletV5.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dictionary, toNano } from 'ton-core';
import { WalletId, WalletV5 } from '../wrappers/wallet-v5';
import { compile, NetworkProvider } from '@ton-community/blueprint';
import { compile, NetworkProvider } from '@ton/blueprint';
import { LibraryDeployer } from '../wrappers/library-deployer';
import { getSecureRandomBytes, keyPairFromSeed } from 'ton-crypto';

Expand All @@ -15,11 +15,11 @@ export async function run(provider: NetworkProvider) {
const walletV5 = provider.open(
WalletV5.createFromConfig(
{
signature_auth_disabled: false,
signatureAllowed: true,
seqno: 0,
walletId: new WalletId({ networkGlobalId: -3 }).serialized, // testnet
publicKey: keypair.publicKey,
extensions: Dictionary.empty()
extensions: Dictionary.empty() as any
},
LibraryDeployer.exportLibCode(await compile('wallet_v5'))
)
Expand Down