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

new multisig #74

Merged
merged 61 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
45fa13b
move to common types + serialize method
dorin-iancu May 29, 2024
3933260
move functions
dorin-iancu May 29, 2024
2b782c7
change user mapper + hash in serialize method
dorin-iancu May 30, 2024
412eed7
common methods
dorin-iancu May 30, 2024
2704805
move some code
dorin-iancu May 30, 2024
1f457b1
actual signatures
dorin-iancu Jun 5, 2024
7a8ab16
temp comment
dorin-iancu Jun 5, 2024
32e1f99
clippy
dorin-iancu Jun 6, 2024
69473f9
arg to struct
dorin-iancu Jun 10, 2024
6806a42
remove comment
dorin-iancu Jun 10, 2024
3ead8e8
action type to hash
dorin-iancu Jun 10, 2024
1267ed4
propose for someone else
dorin-iancu Jun 10, 2024
72ba396
Merge branch 'main' into impl-for-real-now
dorin-iancu Jun 10, 2024
e60de52
annotation
dorin-iancu Jun 10, 2024
53d9d77
add-sc-back
dorin-iancu Jun 13, 2024
c284542
Merge pull request #73 from multiversx/add-sc-back
dorin-iancu Jun 13, 2024
f9a62c5
Merge pull request #69 from multiversx/impl-for-real-now
dorin-iancu Jun 13, 2024
a44d2dd
cleanup
dorin-iancu Jun 13, 2024
e4bb20b
common function
dorin-iancu Jun 14, 2024
2e24e01
move action execution in another file
dorin-iancu Jun 14, 2024
b3f7293
separate callbacks module
dorin-iancu Jun 14, 2024
3d59fef
endpoints to separate files
dorin-iancu Jun 14, 2024
440c5b1
Merge pull request #71 from multiversx/propose-other-member
dorin-iancu Jun 17, 2024
d80c913
ugdate actions
dorin-iancu Jun 17, 2024
4269ed2
partly impl
dorin-iancu Jun 17, 2024
c40dcf6
propose endpoints
dorin-iancu Jun 17, 2024
4e4ab89
move function
dorin-iancu Jun 17, 2024
9b4d38c
comment
dorin-iancu Jun 17, 2024
d6bed26
split function
dorin-iancu Jun 17, 2024
1d855fb
remove addr endpoint
dorin-iancu Jun 17, 2024
b73b8e2
execute action directly
dorin-iancu Jun 17, 2024
f309783
check module status
dorin-iancu Jun 17, 2024
e1cc79f
view
dorin-iancu Jun 17, 2024
0bdf726
Merge pull request #75 from multiversx/extra-modules
dorin-iancu Jun 20, 2024
0b00925
Update cargo.lock.
andreibancioiu Jun 20, 2024
2724791
Merge branch 'feat/new-multisig' into modules-impl
andreibancioiu Jun 20, 2024
ab2c28f
Merge branch 'main' into merge-feat-new-multisig-with-main
dorin-iancu Jul 1, 2024
3d32f3b
Merge branch 'main' into merge-feat-new-multisig-with-main
dorin-iancu Jul 3, 2024
299b19f
merge and upgrade
dorin-iancu Jul 3, 2024
47ba953
Merge pull request #87 from multiversx/merge-feat-new-multisig-with-main
dorin-iancu Jul 3, 2024
cd26e60
merge with base
dorin-iancu Jul 3, 2024
e56748e
fix
dorin-iancu Jul 3, 2024
bf04742
fixes after review
dorin-iancu Jul 4, 2024
7b3df18
fix
dorin-iancu Jul 8, 2024
a93f1e1
clippy
dorin-iancu Jul 8, 2024
d63a78c
change arg order
dorin-iancu Jul 8, 2024
8688285
rename
dorin-iancu Jul 8, 2024
0990632
small optimization
dorin-iancu Jul 8, 2024
6ab8821
common error message
dorin-iancu Jul 8, 2024
9f15018
rename
dorin-iancu Jul 8, 2024
845676c
fix clearing issue
dorin-iancu Jul 8, 2024
bf8dee6
Merge pull request #77 from multiversx/modules-impl
dorin-iancu Jul 9, 2024
43acd28
test setup + init test
dorin-iancu Jul 9, 2024
1dd260c
test + bugfix
dorin-iancu Jul 9, 2024
aa628a4
evil clippy
dorin-iancu Jul 9, 2024
f0c380e
derive over impl
dorin-iancu Jul 9, 2024
334161c
Merge pull request #90 from multiversx/multisig-imp-tests
dorin-iancu Jul 11, 2024
5194d5a
Merge branch 'main' into merge-and-upgrade-2
dorin-iancu Jul 11, 2024
33bed61
upgrade
dorin-iancu Jul 11, 2024
15e6d9a
cargo lock
dorin-iancu Jul 11, 2024
e427df9
Merge pull request #94 from multiversx/merge-and-upgrade-2
dorin-iancu Jul 12, 2024
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
35 changes: 27 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ members = [
"contracts/multisig",
"contracts/multisig/meta",
"contracts/multisig/interact",
"contracts/multisig-improved",
"contracts/multisig-improved/meta",
"contracts/mystery-box",
"contracts/mystery-box/meta",
"contracts/nft-escrow",
Expand Down
7 changes: 7 additions & 0 deletions contracts/multisig-improved/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
*/target/

# The mxpy output
output
24 changes: 24 additions & 0 deletions contracts/multisig-improved/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "multisig-improved"
version = "1.0.0"
authors = ["Andrei Marinica <[email protected]>"]
edition = "2021"
publish = false

[lib]
path = "src/multisig_improved.rs"

[dependencies.multiversx-sc]
version = "0.51.1"

[dependencies.multiversx-sc-modules]
version = "0.51.1"

[dev-dependencies.multiversx-sc-scenario]
version = "0.51.1"

[dev-dependencies.adder]
path = "../adder"

[dev-dependencies.factorial]
path = "../factorial"
82 changes: 82 additions & 0 deletions contracts/multisig-improved/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Multisig Smart Contract (MSC)

## Abstract
Cryptocurrencies can be one of the safest ways to store and manage wealth and value. By safeguarding a short list of words, the so-called seed or recovery phrase, anyone can protect thousands or millions of dollars in wealth and rest assured that no hacker or government can take it from them. In practice, it’s never so easy.

One problem is that single-signature addresses rely on protecting a single private key.

A better solution would use for example 2-of-3 multisig (or any combination of M-of-N for M ≤ N) quorum consisting of three separate private keys, held by three separate people or entities, and requiring any two to sign. This provides both security and redundancy since compromising any one key/person does not break the quorum: if one key is stolen or lost, the other two keyholders can sweep funds to another address (protected by a new quorum) by mutually signing a transaction moving the funds.

As an example, let us imagine the following scenario. An institution launches a stablecoin. For safety, it is required that 3 out of 5 designated addresses sign any mint or burn transaction. Alice deploys the multisig SC. She adds Bob, Charlie, Dave and Eve as signers to the contract and sets the quorum to a minimum number of signers to 3. A quorum of signatures is also required to add or remove signers after the initial deployment. If for some reason, Eve’s account is compromised, Alice proposes removing Eve’s address from the signers’ board. Charlie and Dave sign, causing Eve’s address to be removed. There are only 4 addresses now registered in the contract. By the same process, signers could add 2 more addresses to their ranks, and increase the required quorum signatures from 3 to 4.

Thus, essentially the multisig SC (we will refer to it, from now on, as MSC) enables multiple parties to sign or approve an action that takes place - typically a requirement for certain wallets, accounts, and smart contracts to prevent a rogue or hacked individual from performing detrimental actions.

## Multisig transaction flow
On-chain multisig wallets are made possible by the fact that smart contracts can call other smart contracts. To execute a multisig transaction the flow would be:

* A proposer or board member proposes an action.
* The proposed action receives an unique id/hash.
* N board members are notified (off-chain) to review the action with the specific id/hash.
* M out of N board members sign and approve the action.
* Any proposer or board member “performs the action”.

## Design guidelines

The required guidelines are:
* **No external contracts.** Calling methods of other contracts from within the methods of your own MSC is an amazing feature but should not be required for our simple use case. This also avoids exposing us to bugs. Because any arbitrarily complex function call can be executed, the MSC functions exactly as a standard wallet, but requires multiple signatures.

* **No libraries.** Extending the last guideline, our contract has no upstream dependencies other than itself. This minimizes the chance of us misunderstanding or misusing some piece of library code. It also forces us to stay simple and eases auditing and eventually formal verification.

* **Minimal internal state.** Complex applications can be built inside of MultiversX smart contracts. Storing minimal internal state allows our contract’s code to be simpler, and to be written in a more functional style, which is easier to test and reason about.

* **Uses cold-storage.** The proposer which creates an action or spends from the contract has no special rights or access to the MSC. Authorization is handled by directly signing messages by the board members’ wallets that can be hardware wallets (Trezor; Ledger, etc.) or software wallets.

* **Complete end-to-end testing.** The contract itself is exhaustively unit tested, audited and formally verified.

## Roles
* **Deployer** - This is the address that deploys the MSC. By default this address is also the owner of the SC, but the owner can be changed later if required, as this is by default supported by the MultiversX protocol itself. This is the address that initially set up the configuration of the SC: board members, quorum, etc. It is important to mention that at deployment a very important configuration parameter is the option to allow the SC to be upgradeable or not. It is recommended for most use cases the SC to be non-upgradeable. Leaving the SC upgradable will give the owner of the SC the possibility to upgrade the SC and bypass the board, defeating the purpose of a MSC. If keeping the SC upgradeable is desired, a possible approach would be to make the owner another MSC, and both SCs could maintain the same board, so an upgrade action would need the approval of the board.

* **Owner** - The deployer is initially the owner of the MSC, but if desired can be changed later by the current owner to a different owner. If the SC is upgradeable, the owner can also upgrade the SC.

* **Board and quorum** - Multiple addresses need to be previously registered in the MSC, forming its board. A board member needs to be specifically registered as a board member, meaning for example that the owner or deployer of the MSC is not automatically a board member as well. Board members can vote on every action that the MSC performs. Signing a proposed action means the board members agree. Customarily, not all board members will need to sign every action; the MSC will configure how many signatures will be necessary for an action to be performed, the quorum. For instance, such a contract could have 5 board members, but a quorum of 3 would be enough to perform any action (M-of-N or in this case 3-of-5).

* **Proposer** - The proposer is an address whitelisted in the MSC that can propose any action. An action can be any transaction; for example: send 10 eGLD to the treasury, mint more ESDT, etc. All board members are proposers by default but non-board members can be added as well to the list of whitelisted proposers. The proposers can only propose actions that then need to be approved and signed by the board members. The board member that proposes an action doesn’t need to sign it anymore; it is considered signed.

## Functionality
The MSC should be able to perform most tasks that a regular account is able to perform. It should also be as general as possible. This means that it should operate with a generic concept of “Action”, that the board needs to sign before being performed. Actions can interact with the MSC itself (let's call them **internal actions**) or with external addresses or other SC (**external actions**).

External actions have one and only one function, which is to send the action as a transaction whose sender is the MSC. Because any arbitrarily complex function call can be executed, the MSC functions exactly as a standard wallet, but requires multiple signatures.

The types of internal actions should be the following:

* Add a new member to the board.
* Remove a member from the board. This is only allowed if the new board size remains larger than the number of required signatures (quorum). Otherwise a new member needs to be added first.
* Change the quorum: the required number of signatures. Restriction: 1 <= quorum <= board size.
* Add a proposer.
* Remove a proposer.
* Change multisig contract owner (might be relevant for upgrading the MSC).
* Pay functions - by default we recommend the MSC to not be set up as a payable SC and any deposit or send transaction of eGLD or ESDT towards the MSC will need to call the desired pay function (if a transaction is not a call to these 2 functions then it is rejected immediately and the value is sent back to original sender): Deposit and/or Send. By making the MSC not a payable MSC we reduce the risk of users sending into the MSC funds that then are locked in the MSC or need to be manually send back to the user (in case of a mistake). By making the MSC not a payable MSC it also means that any deposit or send transaction needs to explicitly call the deposit or send function of the MSC.

Any external and internal action will follow these steps and process:

* **Propose action:** this will generate an action id. The action id is unique.
* **View action:** the board members need to see the action proposed before they approve it.
* **Sign action:** board members are allowed to sign. We might add an expiration date until board members can sign (until block x…).
* **Un-sign action:** board members are allowed to un-sign, i.e. to remove their signature from an action. Actions with 0 signatures are cleared from storage. This is to allow mistakes to be cleared.
* **Perform action (by id/hash)** - can be activated by proposers or board members. It is successful only if enough signatures are present from the board members. Whoever calls “perform action” needs to provide any eGLD required by the target, as well as to pay for gas. If there is a move balance kind of action, who calls the action pays the gas and the amount to be moved is taken from MSC balance. But the gas is always taken from the balance of the one who creates the "perform action" transaction.

Also the following view functions will be available:
* **Count pending Actions:** returns the number of existing Actions.
* **List latest N pending Actions:** provides hashes of the latest N pending Actions, most recent being 0 and oldest being N-1. Usually called in tandem with Count.

## Initializing the MSC

There are 2 ways to do it:
* Provide all board member addresses and the number of required signatures directly in the constructor.
* Deployer deploys with just herself on the board and required signatures = 1. Then adds all other N-1 signers and sets required signatures to M. This works, but requires many transactions, so the constructor-only approach might be preferred.

MSC is a deployable SC written in Rust and compiled in WASM.

## Conclusion

Multisig accounts are a critical safety feature for all users of the MultiversX ecosystem. Decentralised applications will rely heavily upon multisig security.
13 changes: 13 additions & 0 deletions contracts/multisig-improved/meta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "multisig-improved-meta"
version = "0.0.0"
authors = ["Andrei Marinica <[email protected]>"]
edition = "2021"
publish = false

[dependencies.multisig-improved]
path = ".."

[dependencies.multiversx-sc-meta-lib]
version = "0.51.1"
default-features = false
3 changes: 3 additions & 0 deletions contracts/multisig-improved/meta/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
multiversx_sc_meta_lib::cli_main::<multisig_improved::AbiProvider>();
}
3 changes: 3 additions & 0 deletions contracts/multisig-improved/multiversx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"language": "rust"
}
17 changes: 17 additions & 0 deletions contracts/multisig-improved/sc-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[settings]
main = "main"

[contracts.main]
name = "multisig"
add-unlabelled = true

[contracts.full]
name = "multisig-full"
add-unlabelled = true
add-labels = ["multisig-external-view"]

[contracts.view]
name = "multisig-view"
external-view = true
add-unlabelled = false
add-labels = ["multisig-external-view"]
35 changes: 35 additions & 0 deletions contracts/multisig-improved/src/action_types/discard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::common_types::action::{ActionId, ActionStatus};

multiversx_sc::imports!();

#[multiversx_sc::module]
pub trait DiscardActionModule:
crate::common_functions::CommonFunctionsModule
+ crate::state::StateModule
+ super::external_module::ExternalModuleModule
+ super::propose::ProposeModule
+ super::sign::SignModule
+ super::perform::PerformModule
+ super::execute_action::ExecuteActionModule
+ crate::ms_endpoints::callbacks::CallbacksModule
+ crate::check_signature::CheckSignatureModule
+ crate::external::events::EventsModule
{
fn discard_action(&self, action_id: ActionId) {
require!(
self.get_action_valid_signer_count(action_id) == 0,
"cannot discard action with valid signatures"
);

self.abort_batch_of_action(action_id);
self.clear_action(action_id);
}

fn abort_batch_of_action(&self, action_id: ActionId) {
let batch_id = self.group_for_action(action_id).get();
if batch_id != 0 {
self.action_group_status(batch_id)
.set(ActionStatus::Aborted);
}
}
}
Loading
Loading