-
Notifications
You must be signed in to change notification settings - Fork 546
Multisig-wallet ETH signature support #272
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
Merged
Merged
Changes from 146 commits
Commits
Show all changes
153 commits
Select commit
Hold shift + click to select a range
b5fa1a9
eth-account-abstraction; added initial predicate
K1-R1 af5db60
eth-account-abstraction; updated predicate to use public key
K1-R1 3d3e8e8
eth-account-abstraction; added script for pub key derivation and upda…
K1-R1 2a093ed
eth-account-abstraction; swicthed to using addresses
K1-R1 c218365
eth-account-abstraction; basic ec_recover functionaliy
K1-R1 a935a16
eth-account-abstraction; cleaned up
K1-R1 811bb01
eth-account-abstraction; added panicking test
K1-R1 0cb8945
eth-account-abstraction; add eth_prefix util
K1-R1 a88a38f
eth-account-abstraction; added eip_191_hash util
K1-R1 8fe6638
eth-account-abstraction; added ec_recover_evm_address util
K1-R1 7487a64
Merge branch 'FuelLabs:master' into eth-account-abstraction
K1-R1 78026a0
Added recover.sw and associated test for debugging ec recovery
K1-R1 690a6b3
Synced with master
K1-R1 fa7cc54
Merge branch 'eth-account-abstraction' of github.com:K1-R1/sway-appli…
K1-R1 02e58ef
Updated cargo.toml for linting
K1-R1 1768855
Updated cargo.toml for linting
K1-R1 ceb979d
Added test for debugging with sway script
K1-R1 a46c665
Setup for EVM address
K1-R1 da5a121
Renamed recover script
K1-R1 7a75222
Setup for verifying address hashing in Sway
K1-R1 4f903f1
Setup testing ec_recover from known valid signature
K1-R1 2a4dfb9
Merge branch 'FuelLabs:master' into eth-account-abstraction
K1-R1 2c2542a
Setup for testing Secretkey creation via different methods
K1-R1 6e9efed
Setup for testing signature creation methods
K1-R1 9ad6ca8
Cleaned up testing signature creation methods
K1-R1 6f08846
Setup for working with correct values via sign_compact_recoverable
K1-R1 47f0d05
Merge branch 'FuelLabs:master' into eth-account-abstraction
K1-R1 0ea68e8
Merge branch 'eth-account-abstraction' of github.com:K1-R1/sway-appli…
K1-R1 5f4a8f0
Updated reference/target values
K1-R1 441a46c
Cleaned up; setup for recovery of evm address
K1-R1 aad7dcd
Merge branch 'FuelLabs:master' into eth-account-abstraction
K1-R1 5078788
Updated to forc 0.30 and fuels 0.28
K1-R1 bf861e9
Updated to use SDK's sign_message
K1-R1 a9c1a7e
Added panicing test
K1-R1 3f14c7b
Updated eip_191_format and added utils as dep
K1-R1 deee0d7
Added sha3 as dependency
K1-R1 0b7a606
Setup for testing inputs of hashing during eip-191 formatting
K1-R1 febbf02
Setup for comparison of bit-shifted data
K1-R1 92200b1
Updated to new compose & decompose functions
K1-R1 3d56c14
Setup for testing eip-191 formatting, with temp padded data
K1-R1 f0ff4a7
Updated encode_data docs
K1-R1 5be5769
Setup for testing single signature, with EIP-191(padded) and Eth-pref…
K1-R1 cafe029
Updated signing mechanism and refactored test utils
K1-R1 f5079b5
Single signature, with EIP-191 format and Eth-prefix, recovery
K1-R1 d0b4fbc
Merge branch 'master' into eth-account-abstraction
K1-R1 9c796a9
forc-fmt
K1-R1 88641ec
fmt
K1-R1 2c6297b
Merge branch 'eth-account-abstraction' of github.com:K1-R1/sway-appli…
K1-R1 be71d20
Switched to contract to test updated count_approval function
K1-R1 1087996
Multi-sig style account abstracted signature recovery
K1-R1 451477b
Refactored utils
K1-R1 cef95c6
Resolved errors from forc updates
K1-R1 83e653b
Updated constructor
K1-R1 f29e402
Updated execute_transaction
K1-R1 46cbdbf
Updated transfer
K1-R1 43a8fd1
Removed is_owner
K1-R1 0a1f6dd
Updated transaction_hash
K1-R1 43edf29
Updated nonce
K1-R1 23bb94b
Updated create_hash
K1-R1 3be0f77
Added account abstracted count_approvals functionality
K1-R1 a9b52d5
Updated interface imports
K1-R1 c6cf191
Renamed to interface
K1-R1 17f8ec0
Updated documentation
K1-R1 f05074d
Refactored utils
K1-R1 7e9dfff
Removed pub from count_approvals
K1-R1 ab9bae9
Multi-Sig setup for basic account abstraction test
K1-R1 16c076f
Updated forc and fuels versions
K1-R1 2302e30
Refactor: transfer
K1-R1 8b843bb
Setup for basic full transfer test
K1-R1 76bfef1
Refactor: tests/utils/mod.rs and cleared up docs
K1-R1 3487afa
Removed unused imports
K1-R1 192d2a4
Added abi calls
K1-R1 03792db
Added test_helpers
K1-R1 0ab1c65
Added deps
K1-R1 43253c1
Refactored encode_data
K1-R1 465ef48
Setup full testing format
K1-R1 46bddbd
Added function testing
K1-R1 7aaf21f
Added gets_transaction_hash test
K1-R1 3da0cbd
Refactored create_hash
K1-R1 7e5869e
Removed unused imports and fixed signature variable definition
K1-R1 2e76d37
Refactored gets_transaction_hash
K1-R1 b35bc76
Removed eth-account-abstraction dir
K1-R1 c5f6c0e
Removed unused deps
K1-R1 c18cd5b
Refactored multisig-wallet dir ahead of merge
K1-R1 0c5683a
Merge branch 'master' into eth-account-abstraction
K1-R1 a1040d4
CI; update forc
K1-R1 18e64ee
Updated in-code docs
K1-R1 92c0456
Updated README
K1-R1 6edef9d
Refactored for contributing book code style compliance
K1-R1 0786c8a
Merge branch 'master' into eth-account-abstraction
K1-R1 a65829f
Update multisig-wallet/project/multisig-contract/src/data_structures.sw
K1-R1 b03e4c9
Update multisig-wallet/project/multisig-contract/src/interface.sw
K1-R1 bc9f715
Update multisig-wallet/project/multisig-contract/src/main.sw
K1-R1 7021170
Update multisig-wallet/project/multisig-contract/src/main.sw
K1-R1 94c196c
Update multisig-wallet/project/multisig-contract/src/main.sw
K1-R1 268d9b9
Update multisig-wallet/project/multisig-contract/src/interface.sw
K1-R1 87a56fc
Update multisig-wallet/project/multisig-contract/src/utils.sw
K1-R1 2eda802
Update multisig-wallet/project/multisig-contract/src/utils.sw
K1-R1 4d29183
Fixed grammar
K1-R1 83856e4
Refactored SignatueData and added field docs
K1-R1 6b6f91f
Updated constructor docs
K1-R1 e25a440
Refactored SignatureData within format_and_sign
K1-R1 cb4bcc6
Removed imports already contained within prelude
K1-R1 d5b677a
Refactored single imports
K1-R1 f46d333
CI: Forc.toml
K1-R1 65e0165
Docs: replaced Panics with Reverts
K1-R1 0565134
Refactored comments and removed unneeded comments
K1-R1 1e47767
Reworder SignaturData docs
K1-R1 9857641
Renamed SignatureData fields
K1-R1 3ed342e
Renamed SignatureData => SignatureInfo
K1-R1 0868bf1
Renamed signatures_data => signatures and signature_data => signature…
K1-R1 8983968
Swicthed to backticks
K1-R1 41f4571
Ordered event fields
K1-R1 5da897a
Refactored User docs
K1-R1 2df99ee
Added check in constructor for threshold <= sum of weights
K1-R1 d6f49b5
recover_signer; use propagated error from ec_recover_address
K1-R1 9e06684
Added link to EIP-191 and EIP-191 constants
K1-R1 50deb41
Renamed encode_and_pack_signed_data and refactored eip_191_personal_s…
K1-R1 c890b59
Added cancel_transaction
K1-R1 54adbd9
Refactored utils
K1-R1 56c6f33
Ethereum Prefix now uses Keccak256
K1-R1 b84cbc7
Updated ethereum_prefix docs
K1-R1 c41190f
Refactored tests with utility parameters
K1-R1 ecfae8a
Refactored revert module into alphabetical order
K1-R1 7bd42d5
Added paths module
K1-R1 a150a89
Merge branch 'master' into eth-account-abstraction
K1-R1 afcc4b9
Added TODO regarding use of unsafe blocks
K1-R1 5d351b7
Added EIP-191 info to README
K1-R1 3b6fc39
Added specification and sequence diagram
K1-R1 7993c16
Merge branch 'eth-account-abstraction' of github.com:K1-R1/sway-appli…
K1-R1 a2f11a7
Added template tests for execute_transaction
K1-R1 83ab9c9
Bumped versions and refactored utils
K1-R1 54cf730
Refactored utils and added comment
K1-R1 59dcbbd
Bump in line with repo
K1-R1 57680e9
Merge branch 'master' into eth-account-abstraction
K1-R1 8551307
Removed redundent headings
K1-R1 04b91d9
Added suggested assertion to balance test
K1-R1 bd0956d
Update multisig-wallet/project/multisig-contract/tests/functions/canc…
K1-R1 aeab66d
Reordered tests
K1-R1 cb2b456
Added suggestion to constructor test
K1-R1 c196517
Added should panic errors
K1-R1 366a5d9
Update multisig-wallet/project/multisig-contract/tests/functions/nonc…
K1-R1 dc24542
Updated test assertions
K1-R1 0acd000
Removed redundant transfer
K1-R1 de3315e
Renamed constructor_users to defaul_users
K1-R1 d387dbb
Merge branch 'eth-account-abstraction' of github.com:K1-R1/sway-appli…
K1-R1 e142aba
Updated Bytes ToDos
K1-R1 9f75e9e
Renamed contract binding type
K1-R1 dbabed7
Refactored
K1-R1 ccf4e26
Renamed abi_calls to interface
K1-R1 9290fa4
Added testing for logs
K1-R1 94255e2
Merge branch 'master' into eth-account-abstraction
K1-R1 d7a6907
Merge branch 'master' into eth-account-abstraction
simonr0204 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or 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 |
---|---|---|
@@ -1,5 +1,3 @@ | ||
[workspace] | ||
resolver = "2" | ||
members = [ | ||
"./project/multisig-contract", | ||
] | ||
members = ["./project/multisig-contract"] |
This file contains hidden or 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
This file contains hidden or 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,97 @@ | ||
Table of Contents | ||
|
||
- [Overview](#overview) | ||
- [Use Cases](#use-cases) | ||
- [Core Functionality](#core-functionality) | ||
- [`cancel_transaction()`](#cancel_transaction) | ||
- [`constructor()`](#constructor) | ||
- [`execute_transaction()`](#execute_transaction) | ||
- [`transfer()`](#transfer) | ||
- [State Checks](#state-checks) | ||
- [`nonce()`](#nonce) | ||
- [`balance()`](#balance) | ||
- [Utilities](#Utilities) | ||
- [`transaction_hash()`](#transaction_hash) | ||
- [Sequence Diagram](#sequence-diagram) | ||
|
||
# Overview | ||
|
||
This document provides an overview of the application. | ||
|
||
It outlines the use cases, i.e., desirable functionality, in addition to requirements for the smart contracts. | ||
|
||
# Use Cases | ||
|
||
This section contains general information about the functionality of the application and thus does not touch upon any technical aspects. | ||
|
||
If you are interested in a functional overview then this is the section for you. | ||
|
||
#### Core Functionality | ||
|
||
##### `cancel_transaction()` | ||
|
||
1. Cancels the next transaction by spending the current nonce. This is both a safety mechanism enabling a user to explicitly render a previously shared signature useless, as well as a way of conveniently skipping a transaction. | ||
1. If the caller is an owner, which requires the contract to have been initialised. | ||
|
||
##### `constructor()` | ||
|
||
1. Sets the parameters for approving a transaction and sets the owners of the multisig. | ||
1. If the constructor hasn't already been called. | ||
2. Requires the `threshold`; the number of approvals required for a transaction to occur. | ||
1. If the `threshold` is not 0. | ||
3. Requires the `users`; the information about the owners of the multisig | ||
1. If none of the owners have the 0th address (0x00000...). | ||
2. If none of the owners are set to have an approval weighting (number of approvals per owner) of 0. | ||
3. If the sum of the owners' approval weightings is a value larger than the `threshold` parameter. This prevents the contract being setup when the owners can never submit enough approvals to allow a transaction. | ||
|
||
##### `execute_transaction()` | ||
|
||
1. Execute a transaction, formed from the parameters. | ||
> **NOTE** This functionality is not yet fully implemented. | ||
1. If the constructor has been called. | ||
2. If signature recovery is successful. | ||
3. If the recovered addresses are in ascending order. | ||
4. If the number of approvals, from the owners whose addresses were recovered, meets the threshold. | ||
5. Requires `data`; the data field of the transaction to be executed. | ||
6. Requires `signatures`; The information for each of the signatures submitted to approve a specific transaction. | ||
7. Requires `to`; The recipient of the transaction to be executed. | ||
8. Requires `value`; The value sent in the transaction to be executed. | ||
|
||
##### `transfer()` | ||
|
||
1. Transfers assets, via a transaction formed from the parameters. | ||
1. If the constructor has been called. | ||
2. If signature recovery is successful. | ||
3. If the recovered addresses are in ascending order. | ||
4. If the number of approvals, from the owners whose addresses were recovered, meets the threshold. | ||
5. Requires `asset_id`: the contract ID of the asset to be transferred. | ||
6. Requires `data`; the data field of the transaction. | ||
7. Requires `signatures`; The information for each of the signatures submitted to approve a specific transaction. | ||
8. Requires `to`; The recipient of the transaction. | ||
9. Requires `value`; The value sent in the transaction. | ||
1. If the contract owns enough of the asset to be transferred. | ||
|
||
#### State Checks | ||
|
||
##### `nonce()` | ||
|
||
1. Returns the current nonce of the contract. | ||
|
||
##### `balance()` | ||
|
||
1. Returns the contract's balance of the specified asset. | ||
1. Requires `asset_id`; The contract ID of the asset to check that balance of. | ||
|
||
#### Utilities | ||
|
||
##### `transaction_hash()` | ||
|
||
1. Returns the hash of a transaction, comprised of the parameters. This is a utility for getting a transaction hash to sign over. | ||
1. Requires `data`; The data field of the transaction. | ||
2. Requires `nonce`; The nonce field of the transaction. | ||
3. Requires `to`; The recipient of the transaction. | ||
4. Requires `value`; The value sent in the transaction. | ||
|
||
## Sequence Diagram | ||
|
||
 |
This file contains hidden or 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
27 changes: 0 additions & 27 deletions
27
multisig-wallet/project/multisig-contract/src/contract_abi.sw
This file was deleted.
Oops, something went wrong.
52 changes: 39 additions & 13 deletions
52
multisig-wallet/project/multisig-contract/src/data_structures.sw
This file contains hidden or 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 |
---|---|---|
@@ -1,24 +1,50 @@ | ||
library data_structures; | ||
|
||
pub struct User { | ||
// Contracts cannot sign therefore restrict scope to Address | ||
/// The wallet address of a user | ||
identity: Address, | ||
/// Number of approvals the user provides when approving. | ||
/// The default is usually 1 | ||
weight: u64, | ||
use std::b512::B512; | ||
|
||
pub enum MessageFormat { | ||
None: (), | ||
EIP191PersonalSign: (), | ||
} | ||
|
||
pub enum MessagePrefix { | ||
None: (), | ||
Ethereum: (), | ||
} | ||
|
||
pub enum WalletType { | ||
Fuel: (), | ||
EVM: (), | ||
} | ||
|
||
pub struct SignatureInfo { | ||
/// The type of formatting of the message that was signed. | ||
message_format: MessageFormat, | ||
/// The type of prefix prepended to the message that was signed. | ||
message_prefix: MessagePrefix, | ||
/// The signature generated by signing over a message hash with the format and prefix specified in the `format` and `prefix` fields. | ||
signature: B512, | ||
/// The wallet type of the signer of the message. | ||
wallet_type: WalletType, | ||
} | ||
|
||
pub struct Transaction { | ||
/// Unique identifier for the contract which prevents this Tx from being submitted to another | ||
/// instance of the multisig | ||
/// Unique identifier for the contract which prevents this transaction from being submitted to another | ||
/// instance of the multisig. | ||
contract_identifier: ContractId, | ||
/// Payload sent to destination // TODO: change to vec when implemented | ||
/// Payload sent to destination // TODO: change to Bytes when implemented: https://github.com/FuelLabs/sway/pull/3454 | ||
K1-R1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data: b256, | ||
/// The recipient (output / contract) regarding the Tx details | ||
/// The recipient (output / contract) regarding the transaction details. | ||
destination: Identity, | ||
/// Value used to prevent double spending | ||
/// Value used to prevent double spending. | ||
nonce: u64, | ||
/// Amount of asset | ||
/// Amount of asset. | ||
value: u64, | ||
} | ||
|
||
pub struct User { | ||
/// The wallet address of a user. | ||
address: b256, | ||
K1-R1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// The number of approvals the user provides when approving. | ||
weight: u64, | ||
} |
This file contains hidden or 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 |
---|---|---|
@@ -1,13 +1,20 @@ | ||
library errors; | ||
|
||
pub enum AccessControlError { | ||
CanOnlyBeAccessedByAnOwner: (), | ||
} | ||
|
||
pub enum ExecutionError { | ||
IncorrectSignerOrdering: (), | ||
InsufficientAssetAmount: (), | ||
InsufficientApprovals: (), | ||
} | ||
|
||
pub enum InitError { | ||
AddressCannotBeZero: (), | ||
CannotReinitialize: (), | ||
NotInitialized: (), | ||
ThresholdCannotBeZero: (), | ||
TotalWeightCannotBeLessThanThreshold: (), | ||
WeightingCannotBeZero: (), | ||
} |
This file contains hidden or 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
93 changes: 93 additions & 0 deletions
93
multisig-wallet/project/multisig-contract/src/interface.sw
This file contains hidden or 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,93 @@ | ||
library interface; | ||
|
||
dep data_structures; | ||
|
||
use data_structures::{SignatureInfo, User}; | ||
|
||
abi MultiSignatureWallet { | ||
/// Cancel the next transaction by spending the current nonce. | ||
/// | ||
/// # Reverts | ||
/// | ||
/// * When the caller is not setup as an owner. | ||
#[storage(read, write)] | ||
fn cancel_transaction(); | ||
|
||
/// The constructor initializes the necessary values and unlocks further functionality. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `threshold` - The number of approvals required to enable a transaction to be sent. | ||
/// * `users` - The users of the multisig, who can sign transactions to add their approval. | ||
/// | ||
/// # Reverts | ||
/// | ||
/// * When the constructor is called more than once. | ||
/// * When the user address is the 0th address (0x00000...). | ||
/// * When the threshold is set to 0. | ||
/// * When an owner has an approval weight of 0. | ||
/// * When the threshold is a value greater than the sum of the weights. | ||
#[storage(read, write)] | ||
fn constructor(threshold: u64, users: Vec<User>); | ||
|
||
/// Execute a transaction formed from the `to`, `value` and `data` parameters if the signatures meet the | ||
/// threshold requirement. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `data` - The data field of the transaction. | ||
/// * `signatures` - The information for each user's signature for a specific transaction. | ||
/// * `to` - The recipient of the transaction. | ||
/// * `value` - The value sent in the transaction. | ||
/// | ||
/// # Reverts | ||
/// | ||
/// - When the constructor has not been called to initialize the contract. | ||
/// - When the amount of the asset being sent is greater than the balance in the contract. | ||
/// - When the public key cannot be recovered from a signature. | ||
/// - When the recovered addresses are not in ascending order (0x1 < 0x2 < 0x3...). | ||
/// - When the total approval count is less than the required threshold for execution. | ||
#[storage(read, write)] | ||
fn execute_transaction(data: b256, signatures: Vec<SignatureInfo>, to: Identity, value: u64); | ||
|
||
/// Transfers assets to outputs & contracts if the signatures meet the threshold requirement. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `asset_id` - The contract ID of the asset to be transferred. | ||
/// * `data` - The data field of the transaction. | ||
/// * `signatures` - The information for each user's signature for a specific transaction. | ||
/// * `to` - The recipient of the transaction. | ||
/// * `value` - The value sent in the transaction. | ||
/// | ||
/// # Reverts | ||
/// | ||
/// - When the constructor has not been called to initialize the contract. | ||
/// - When the amount of the asset being sent is greater than the balance in the contract. | ||
/// - When the public key cannot be recovered from a signature. | ||
/// - When the recovered addresses are not in ascending order (0x1 < 0x2 < 0x3...). | ||
/// - When the total approval count is less than the required threshold for execution. | ||
#[storage(read, write)] | ||
fn transfer(asset_id: ContractId, data: b256, signatures: Vec<SignatureInfo>, to: Identity, value: u64); | ||
|
||
/// Returns the current nonce in the contract | ||
#[storage(read)] | ||
fn nonce() -> u64; | ||
|
||
/// Returns the contract's balance of the specified asset. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `asset_id` - The contract ID of the asset to check that balance of. | ||
fn balance(asset_id: ContractId) -> u64; | ||
|
||
/// Takes in transaction data and hashes it into a unique tx hash. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `data` - The data field of the transaction. | ||
/// * `nonce` - The nonce field of the transaction. | ||
/// * `to` - The recipient of the transaction. | ||
/// * `value` - The value sent in the transaction. | ||
fn transaction_hash(data: b256, nonce: u64, to: Identity, value: u64) -> b256; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.