diff --git a/README.md b/README.md index aa18f3d..6c07613 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,104 @@ + + # OnChainMultiSig -This repo is the source code for the `OnChainMultiSig` contract. -Its usage is demonstrated with the `MultiSigFlowToken` contract. +This repository is the source code for the `OnChainMultiSig` contract. + +The `OnChainMultiSig` contract is designed to address the need for +multiple signature to authorise transactions without [time constraint] or [gas balance]. + +## Motivation + +The motivation for this contract is in three parts: + +1. **Limited Time Constraint** Natively, Flow suports multiple signers to authorise account transaction +with the innovative [`Weighted Keys`] in the [`Accounts`] system. +However, transactions all have expiration window (measured in blocks). +This is about [10 minutes] on the Mainnet. +It may not always be feasible or user friendly to require multisig key holder to be +present to sign everytime a transaction is required. + +2. **Standardisation** It is common for an account to have multiple resources in their storage path and that +these resources all require the multisig feature. It will be easier for frontend developers or users to compose +signatures that captures the intention of the signer securely. + +3. **Gas requirement** It should not be required that the signers of a multisig resource must +all have balance in an account, the signatures themselves should be enough to authorise +transactions of which some other [`payer`] (or themselves) can pay for. + +## Solution + +To address the time constraint, as the name suggests, the signatures are temporarily stored on chain. +Once all required signatures are ready, anyone can call the public method to execute the transaction. + +Interfaces are defined to facilitate standardising this onchain signature storage across different resources. + +Finally, following Flow's decoupling principle between account and keys, +the signature for a multisig transaction can be sumbitted by a trusted [`payer`], +independent to the account that owns the resource[^1] or the key. +This allows signers to simply be some entity that holds some private key where the corresponding public +key has been added as part of the resource's multisig public key list with some weight. + +### Method Details + +`OnChainMultiSig` contract provides a `Manager` resource which is intended to be created and stored by resources that +supports onchain multisig. +In addition, `PublicSigner` interface is provided for the resources as a standard interface for transactions to: + +1. `addNewPayload`: Create a new payload and signature for it to be stored. +The `TxIndex` must be the current index incremented by one and is included in the signature. +Signature must be produced by the public key in `@Manager.keyList` +2. `addPayloadSignature`: Submit a signature for a payload that was added. +Signature must be produced by the public key in `@Manager.keyList` +3. `executeTx`: Execute a transaction (if all signatures required have been submitted) + +and queries for: + +1. `UUID`: gets the uuid of the multisig resource +2. `getTxIndex`: gets the sequentially assigned current txIndex of multisig pending tx of this resource +3. `getSignerKeys`: gets the list of public keys for the resource's multisig signers +4. `getSignerKeyAttr`: gets the stored key attributes + +Internal to the `Manager` resource, it implements the `SignatureManager` interface which allows the implementation of `PublicSigner` +functions on the multisig supported resources to work with the `Manager`. + +#### Usage + +We will use a simple `Vault` resource in the `MultiSigFlowToken` contract to demonstrate the usage of the `PublicSigner`, +how to form a [onchain-multisig signature], +transacting with the `MultiSigFlowToken` contract and [resource owner account management]. + +TODO code walk through + +### Signatures + +The message in the signature verified by the `Manager` resource are as such: +**TODO details** + +- `OnChainMultiSig.PayloadDetails` +- only a few types are supported at the moment +- only certain `signatureAlgorithm` and `hashAlgorithm` are supported at the moment + +### Resource Owner Account Management + +Whilst it is possible to allow for onchain multisig feature to be available for resources, +to limit the use to *just* be in that way will ultimately depend on the account that owns the resources. + +As such, the account with such a resource should itself have all the keys added so that the `weights` +of each authorizer is consistent for the resource and the account. This is because if one key +is added to the account, that key has the ability to directly call functions in `Manager` to alter the states. + +Another approach may be that once the resource has been added, all keys for the owner account is revoked. +This limits the flexibility of the account but it may be neccessary, similar to [immutable contracts] in Flow. + +[onchain-multisig signature]: (#signatures) +[immutable contracts]: +[decoupled]: +[10 minutes]: +[`payer`]: +[gas balance]: +[time constrains]: +[`Accounts`]: +[`Weighted Keys`]: +[resource owner account management]: (#resource-owner-account-management) +[^1]: Please see [resource owner account management] for details diff --git a/contracts/MultiSigFlowToken.cdc b/contracts/MultiSigFlowToken.cdc index 29fc76f..8022d15 100644 --- a/contracts/MultiSigFlowToken.cdc +++ b/contracts/MultiSigFlowToken.cdc @@ -28,7 +28,7 @@ pub contract MultiSigFlowToken: FungibleToken { FungibleToken.Receiver, FungibleToken.Balance, OnChainMultiSig.PublicSigner, - OnChainMultiSig.PrivateKeyManager { + OnChainMultiSig.KeyManager { // holds the balance of a users tokens pub var balance: UFix64 @@ -36,7 +36,7 @@ pub contract MultiSigFlowToken: FungibleToken { // initialize the balance at resource creation time init(balance: UFix64) { self.balance = balance; - self.signatureStore = OnChainMultiSig.SignatureStore(publicKeys: [], pubKeyAttrs: []); + self.multiSigManager <- OnChainMultiSig.createMultiSigManager(publicKeys: [], pubKeyAttrs: []) } @@ -53,48 +53,37 @@ pub contract MultiSigFlowToken: FungibleToken { vault.balance = 0.0 destroy vault } - - // PublicSigner interface requirements - // 1. signatureStore: Stores the payloads, transactions pending to be signed and signature - // 2. addNewPayload: add new transaction payload to the signature store waiting for others to sign - // 3. addPayloadSignature: add signature to store for existing paylaods by payload index - // 4. executeTx: attempt to execute the transaction at a given index after required signatures have been added - // 5. UUID: gets the uuid of this resource - // Interfaces 1-3 uses `OnChainMultiSig.Manager` struct for code implementation - // Interface 4 needs to be implemented specifically for each resource - - /// struct to keep track of partial sigatures - pub var signatureStore: OnChainMultiSig.SignatureStore; - /// To submit a new paylaod, i.e. starting a new tx requiring more signatures + // + // Below resource and interfaces are required for any resources wanting to use OnChainMultiSig + // + + // Resource to keep track of partial sigatures and payloads, required for onchain multisig features. + // Limited to `access(self)` to avoid exposing all functions in `SignatureManager` interface to account owner(s) + access(self) let multiSigManager: @OnChainMultiSig.Manager; + + /// To submit a new paylaod, i.e. starting a new tx requiring, potentially requiring more signatures pub fun addNewPayload(payload: OnChainMultiSig.PayloadDetails, publicKey: String, sig: [UInt8]) { - let manager = OnChainMultiSig.Manager(sigStore: self.signatureStore); - let newSignatureStore = manager.addNewPayload(resourceId: self.uuid, payload: payload, publicKey: publicKey, sig: sig); - self.signatureStore = newSignatureStore + self.multiSigManager.addNewPayload(resourceId: self.uuid, payload: payload, publicKey: publicKey, sig: sig); } /// To submit a new signature for a pre-exising payload, i.e. adding another signature pub fun addPayloadSignature (txIndex: UInt64, publicKey: String, sig: [UInt8]) { - let manager = OnChainMultiSig.Manager(sigStore: self.signatureStore); - let newSignatureStore = manager.addPayloadSignature(resourceId: self.uuid, txIndex: txIndex, publicKey: publicKey, sig: sig); - self.signatureStore = newSignatureStore + self.multiSigManager.addPayloadSignature(resourceId: self.uuid, txIndex: txIndex, publicKey: publicKey, sig: sig); } /// To execute the multisig transaction iff conditions are met + /// `configureKey` and `removeKey` functions can be used for all resources if see fit + /// other methods must be implemented to suit the particular resource pub fun executeTx(txIndex: UInt64): @AnyResource? { - let manager = OnChainMultiSig.Manager(sigStore: self.signatureStore); - let exeDetails = manager.readyForExecution(txIndex: txIndex) ?? panic ("no transactable payload at given txIndex") - let p = exeDetails.payload - self.signatureStore = exeDetails.signatureStore + let p = self.multiSigManager.readyForExecution(txIndex: txIndex) ?? panic ("no transactable payload at given txIndex") switch p.method { case "configureKey": let pubKey = p.args[0] as? String ?? panic ("cannot downcast public key"); let weight = p.args[1] as? UFix64 ?? panic ("cannot downcast weight"); - let newSignatureStore = manager.configureKeys(pks: [pubKey], kws: [weight]) - self.signatureStore = newSignatureStore; + self.multiSigManager.configureKeys(pks: [pubKey], kws: [weight]) case "removeKey": let pubKey = p.args[0] as? String ?? panic ("cannot downcast public key"); - let newSignatureStore = manager.removeKeys(pks: [pubKey]) - self.signatureStore = newSignatureStore; + self.multiSigManager.removeKeys(pks: [pubKey]) case "withdraw": let amount = p.args[0] as? UFix64 ?? panic ("cannot downcast amount"); return <- self.withdraw(amount: amount); @@ -116,20 +105,37 @@ pub contract MultiSigFlowToken: FungibleToken { return self.uuid; }; + pub fun getTxIndex(): UInt64 { + return self.multiSigManager.txIndex + } + + pub fun getSignerKeys(): [String] { + return self.multiSigManager.getSignerKeys() + } + pub fun getSignerKeyAttr(publicKey: String): OnChainMultiSig.PubKeyAttr? { + return self.multiSigManager.getSignerKeyAttr(publicKey: publicKey) + } + + // + // --- end of `OnChainMultiSig.PublicSigner` interfaces + // + + // + // Optional Priv Capbilities for owner of the vault to add / remove keys `OnChainMultiSig.KeyManager` + // + // These follows the usual account authorization logic + // i.e. if it is an account with multiple keys, then the total weight of the signatures must be > 1000 pub fun addKeys( multiSigPubKeys: [String], multiSigKeyWeights: [UFix64]) { - let manager = OnChainMultiSig.Manager(sigStore: self.signatureStore); - let newSignatureStore = manager.configureKeys(pks: multiSigPubKeys, kws: multiSigKeyWeights) - self.signatureStore = newSignatureStore; + self.multiSigManager.configureKeys(pks: multiSigPubKeys, kws: multiSigKeyWeights) } pub fun removeKeys( multiSigPubKeys: [String]) { - let manager = OnChainMultiSig.Manager(sigStore: self.signatureStore); - let newSignatureStore = manager.removeKeys(pks: multiSigPubKeys) - self.signatureStore = newSignatureStore; + self.multiSigManager.removeKeys(pks: multiSigPubKeys) } destroy() { MultiSigFlowToken.totalSupply = MultiSigFlowToken.totalSupply - self.balance + destroy self.multiSigManager } } diff --git a/contracts/OnChainMultiSig.cdc b/contracts/OnChainMultiSig.cdc index a7c3c8e..4a0dd88 100644 --- a/contracts/OnChainMultiSig.cdc +++ b/contracts/OnChainMultiSig.cdc @@ -17,16 +17,6 @@ pub contract OnChainMultiSig { } } - pub struct ExecutionDetails { - pub var payload: OnChainMultiSig.PayloadDetails; - pub var signatureStore: OnChainMultiSig.SignatureStore; - - init(payload: OnChainMultiSig.PayloadDetails, signatureStore: OnChainMultiSig.SignatureStore) { - self.payload = payload - self.signatureStore = signatureStore - } - } - pub struct PubKeyAttr{ pub let sigAlgo: UInt8; pub let weight: UFix64 @@ -47,62 +37,101 @@ pub contract OnChainMultiSig { } } + /// Public Signer + /// + /// These interfaces is intended for public usage, a resource that stores the @Manager should implement + /// + /// 1. addNewPayload: add new transaction payload to the signature store waiting for others to sign + /// 2. addPayloadSignature: add signature to store for existing paylaods by payload index + /// 3. executeTx: attempt to execute the transaction at a given index after required signatures have been added + /// 4. UUID: gets the uuid of this resource + /// 5. getTxIndex: gets the sequentially assigned current txIndex of multisig pending tx of this resource + /// 6. getSignerKeys: gets the list of public keys for the resource's multisig signers + /// 7. getSignerKeyAttr: gets the stored key attributes + /// Interfaces 1&2 use `OnChainMultiSig.Manager` resource for code implementation + /// Interface 3 needs to be implemented specifically for each resource + /// Interfaces 4-7 are useful information to interact with the multiSigManager + /// + /// For example, a `Vault` resource with onchain multisig capabilities should implement these interfaces, + /// see example in "./MultiSigFlowToken" pub resource interface PublicSigner { - pub var signatureStore: SignatureStore; pub fun addNewPayload(payload: PayloadDetails, publicKey: String, sig: [UInt8]); pub fun addPayloadSignature (txIndex: UInt64, publicKey: String, sig: [UInt8]); pub fun executeTx(txIndex: UInt64): @AnyResource?; - pub fun UUID(): UInt64; + pub fun UUID(): UInt64; + pub fun getTxIndex(): UInt64; + pub fun getSignerKeys(): [String]; + pub fun getSignerKeyAttr(publicKey: String): PubKeyAttr?; } - pub resource interface PrivateKeyManager { + /// Key Manager + /// + /// Optional interfaces for owner of the vault to add / remove keys in @Manager. + pub resource interface KeyManager { pub fun addKeys( multiSigPubKeys: [String], multiSigKeyWeights: [UFix64]); pub fun removeKeys( multiSigPubKeys: [String]); } - pub struct interface SignatureManager { - pub fun getSignableData(payload: PayloadDetails): [UInt8]; - pub fun addNewPayload (resourceId: UInt64, payload: PayloadDetails, publicKey: String, sig: [UInt8]): SignatureStore; - pub fun addPayloadSignature (resourceId: UInt64, txIndex: UInt64, publicKey: String, sig: [UInt8]): SignatureStore; - pub fun readyForExecution(txIndex: UInt64): ExecutionDetails?; - pub fun configureKeys (pks: [String], kws: [UFix64]): SignatureStore; - pub fun removeKeys (pks: [String]): SignatureStore; - pub fun verifySigners (payload: PayloadDetails?, txIndex: UInt64?, pks: [String], sigs: [Crypto.KeyListSignature]): UFix64?; + /// Signature Manager + /// + /// These interfaces are minimum required for implementors of `PublicSigner` to work + /// with the @Manager resource + pub resource interface SignatureManager { + pub fun getSignerKeys(): [String]; + pub fun getSignerKeyAttr(publicKey: String): PubKeyAttr?; + pub fun addNewPayload (resourceId: UInt64, payload: PayloadDetails, publicKey: String, sig: [UInt8]); + pub fun addPayloadSignature (resourceId: UInt64, txIndex: UInt64, publicKey: String, sig: [UInt8]); + pub fun readyForExecution(txIndex: UInt64): PayloadDetails?; + pub fun configureKeys (pks: [String], kws: [UFix64]); + pub fun removeKeys (pks: [String]); } - pub struct SignatureStore { - // Transaction index - pub(set) var txIndex: UInt64; + /// Manager + /// + /// The main resource that stores, keys, payloads and signature before all signatures are collected / executed + pub resource Manager: SignatureManager { + + /// Transaction Index + /// + /// The sequenctial identifier for each payload stored. + /// Newly added payload increments this index. + pub var txIndex: UInt64; - // Signers and their weights - // String in hex to be decoded as [UInt8], without "0x" prefix - pub let keyList: {String: PubKeyAttr}; + /// Key List + /// + /// Stores the public keys and their respected attributes. + /// Only public keys stored here can add payload or payload signatures. + /// + /// Public keys stored in hex encoded string format without prefix "0x" + access(self) let keyList: {String: PubKeyAttr}; - // map of an assigned index and the payload - // payload in this case is the script and argument - pub var payloads: {UInt64: PayloadDetails} + /// Payloads + /// + /// A Map of an assigned Transaction Index and the Payload represented + /// by `PayloadDetails` + access(self) let payloads: {UInt64: PayloadDetails} - pub var payloadSigs: {UInt64: PayloadSigDetails} + /// Payload Signatures + /// + /// A Map of assigned Transaction Index and all the added signatures + /// from signers in the `keyList` + access(self) let payloadSigs: {UInt64: PayloadSigDetails} - init(publicKeys: [String], pubKeyAttrs: [PubKeyAttr]){ - assert( publicKeys.length == pubKeyAttrs.length, message: "pubkeys must have associated attributes") - self.payloads = {}; - self.payloadSigs = {}; - self.keyList = {}; - self.txIndex = 0; - - var i: Int = 0; - while (i < publicKeys.length){ - self.keyList.insert(key: publicKeys[i], pubKeyAttrs[i]); - i = i + 1; - } + /// Returns the public keys store in this resource + pub fun getSignerKeys(): [String] { + return self.keyList.keys } - } - pub struct Manager: SignatureManager { + /// Returns the attributes (algo, weight) for a given public key + pub fun getSignerKeyAttr(publicKey: String): PubKeyAttr? { + return self.keyList[publicKey] + } - pub var signatureStore: SignatureStore; - + /// Calculates the bytes of a given payload. + /// This is used to create the message to verify the signatures when + /// they are added + /// + /// Note: Currently only support limited types pub fun getSignableData(payload: PayloadDetails): [UInt8] { var s = payload.txIndex.toBigEndianBytes(); s = s.concat(payload.method.utf8); @@ -129,131 +158,170 @@ pub contract OnChainMultiSig { return s; } - pub fun configureKeys (pks: [String], kws: [UFix64]): SignatureStore { + /// Add / replace stored public keys and respected attributes + /// from `keyList` + pub fun configureKeys (pks: [String], kws: [UFix64]) { var i: Int = 0; while (i < pks.length) { let a = PubKeyAttr(sa: 1, w: kws[i]) - self.signatureStore.keyList.insert(key: pks[i], a) + self.keyList.insert(key: pks[i], a) i = i + 1; } - return self.signatureStore } - pub fun removeKeys (pks: [String]): SignatureStore { + /// Removed stored public keys and respected attributes + /// from `keyList` + pub fun removeKeys (pks: [String]) { var i: Int = 0; while (i < pks.length) { - self.signatureStore.keyList.remove(key:pks[i]) + self.keyList.remove(key:pks[i]) i = i + 1; } - return self.signatureStore } - pub fun addNewPayload (resourceId: UInt64, payload: PayloadDetails, publicKey: String, sig: [UInt8]): SignatureStore { - assert(self.signatureStore.keyList.containsKey(publicKey), message: "Public key is not a registered signer"); + /// Add a new payload, potentially requiring additional signatures from other signers + /// + /// `resourceId`: the uuid of the resource that stores this resource + /// `payload` : the payload of the transaction represented by the `PayloadDetails` struct + /// `publicKey` : the public key (must be in the keyList) that signed the `sig` + /// `sig` : the signature where the message is the signable data of the payload + pub fun addNewPayload (resourceId: UInt64, payload: PayloadDetails, publicKey: String, sig: [UInt8]) { - let txIndex = self.signatureStore.txIndex + UInt64(1); + // if the provided key is not in keyList, tx is rejected + assert(self.keyList.containsKey(publicKey), message: "Public key is not a registered signer"); + + // ensure that the signed txIndex is the next txIndex for this resource + let txIndex = self.txIndex + UInt64(1); assert(payload.txIndex == txIndex, message: "Incorrect txIndex provided in paylaod") - assert(!self.signatureStore.payloads.containsKey(txIndex), message: "Payload index already exist"); - self.signatureStore.txIndex = txIndex; + assert(!self.payloads.containsKey(txIndex), message: "Payload index already exist"); + self.txIndex = txIndex; - // The keyIndex is also 0 for the first key + // the first signature is at keyIndex 0 of the `KeyListSignature` + // Note: `keyIndex` must match the order of the Crypto.KeyList constructed during `verifySigners` let keyListSig = [Crypto.KeyListSignature(keyIndex: 0, signature: sig)] - // check if the payloadSig is signed by one of the account's keys, preventing others from adding to storage + // check if the payloadSig is signed by one of the keys in `keyList`, preventing others from adding to storage + // if approvalWeight is nil, the public key is not in the `keyList` or cannot be verified let approvalWeight = self.verifySigners(payload: payload, txIndex: nil, pks: [publicKey], sigs: keyListSig) if ( approvalWeight == nil) { - panic ("invalid signer") + panic ("Invalid signer") } - self.signatureStore.payloads.insert(key: txIndex, payload); + // insert the payload and the first signature into the resource maps + self.payloads.insert(key: txIndex, payload); let payloadSigDetails = PayloadSigDetails( keyListSignatures: keyListSig, pubKeys: [publicKey] ) - self.signatureStore.payloadSigs.insert( + self.payloadSigs.insert( key: txIndex, payloadSigDetails ) emit NewPayloadAdded(resourceId: resourceId, txIndex: txIndex) - return self.signatureStore } - pub fun addPayloadSignature (resourceId: UInt64, txIndex: UInt64, publicKey: String, sig: [UInt8]): SignatureStore { - assert(self.signatureStore.payloads.containsKey(txIndex), message: "Payload has not been added"); - assert(self.signatureStore.keyList.containsKey(publicKey), message: "Public key is not a registered signer"); + /// Add a new payload signature to an existing stored payload identified by the `txIndex` + /// + /// `resourceId`: the uuid of the resource that stores this resource + /// `txIndex` : the transaction index where the payload was added + /// `publicKey` : the public key (must be in the keyList) that signed the `sig` + /// `sig` : the signature where the message is the signable data of the payload + pub fun addPayloadSignature (resourceId: UInt64, txIndex: UInt64, publicKey: String, sig: [UInt8]) { + assert(self.payloads.containsKey(txIndex), message: "Payload has not been added"); + assert(self.keyList.containsKey(publicKey), message: "Public key is not a registered signer"); - // This is a temp keyListSig list that is used to verify a single signature so we use keyIndex as 0 - // The correct keyIndex will overwrite the 0 after we know it is a valid signature + let currentIndex = self.payloadSigs[txIndex]!.keyListSignatures.length + var i = 0; + while i < currentIndex { + if self.payloadSigs[txIndex]!.pubKeys[i] == publicKey { + panic ("Signature already added for this txIndex") + } + i = i + 1; + } + // this is a temp keyListSig list that is used to verify a single signature so we use `keyIndex` as 0 + // the correct `keyIndex` will overwrite the 0 after we know it is a valid signature var keyListSig = Crypto.KeyListSignature( keyIndex: 0, signature: sig) - - // check if the payloadSig is signed by one of the account's keys, preventing others from adding to storage let approvalWeight = self.verifySigners(payload: nil, txIndex: txIndex, pks: [publicKey], sigs: [keyListSig]) if ( approvalWeight == nil) { - panic ("invalid signer") + panic ("Invalid signer") } - let currentIndex = self.signatureStore.payloadSigs[txIndex]!.keyListSignatures.length + // create the correct `keyIndex` with the current length of all the stored signatures keyListSig = Crypto.KeyListSignature(keyIndex: currentIndex, signature: sig) - self.signatureStore.payloadSigs[txIndex]!.keyListSignatures.append(keyListSig); - self.signatureStore.payloadSigs[txIndex]!.pubKeys.append(publicKey); + + // append signature to resource maps + self.payloadSigs[txIndex]!.keyListSignatures.append(keyListSig); + self.payloadSigs[txIndex]!.pubKeys.append(publicKey); emit NewPayloadSigAdded(resourceId: resourceId, txIndex: txIndex) - return self.signatureStore } - pub fun readyForExecution(txIndex: UInt64): ExecutionDetails? { - assert(self.signatureStore.payloads.containsKey(txIndex), message: "No payload for such index"); - let pks = self.signatureStore.payloadSigs[txIndex]!.pubKeys; - let sigs = self.signatureStore.payloadSigs[txIndex]!.keyListSignatures; + /// Checks to see if the total weights of the signers who signed the transaction + /// is sufficient for transaction to occur + /// + /// The weight system is intended to be the same as accounts + /// https://docs.onflow.org/concepts/accounts-and-keys/#weighted-keys + /// + /// Note: if the transaction is ready, the payload and signatures are removed from the maps and must be executed + pub fun readyForExecution(txIndex: UInt64): PayloadDetails? { + assert(self.payloads.containsKey(txIndex), message: "No payload for such index"); + let pks = self.payloadSigs[txIndex]!.pubKeys; + let sigs = self.payloadSigs[txIndex]!.keyListSignatures; let approvalWeight = self.verifySigners(payload: nil, txIndex: txIndex, pks: pks, sigs: sigs) if (approvalWeight == nil) { return nil } if (approvalWeight! >= 1000.0) { - self.signatureStore.payloadSigs.remove(key: txIndex)!; - let pd = self.signatureStore.payloads.remove(key: txIndex)!; - return ExecutionDetails(payload: pd, signatureStore: self.signatureStore); + self.payloadSigs.remove(key: txIndex)!; + let pd = self.payloads.remove(key: txIndex)!; + return pd } else { return nil } } + /// Verifies the signature matches the `payload` or the `txIndex`, exclusively. + /// We do not match the payload from a txIndex and the provided, therefore we reject caller if both are provided. + /// + /// The total weight of valid sigatures is returned, if any. pub fun verifySigners (payload: PayloadDetails?, txIndex: UInt64?, pks: [String], sigs: [Crypto.KeyListSignature]): UFix64? { - assert(payload != nil || txIndex != nil, message: "cannot verify signature without payload or txIndex"); - assert(!(payload != nil && txIndex != nil), message: "cannot verify signature without payload or txIndex"); - assert(pks.length == sigs.length, message: "cannot verify signatures without corresponding public keys"); + assert(payload != nil || txIndex != nil, message: "Cannot verify signature without payload or txIndex"); + assert(!(payload != nil && txIndex != nil), message: "can only verify signature for either payload or txIndex"); + assert(pks.length == sigs.length, message: "Cannot verify signatures without corresponding public keys"); var totalAuthorisedWeight: UFix64 = 0.0; var keyList = Crypto.KeyList(); + + // get the message of the signature var payloadInBytes: [UInt8] = [] if (payload != nil) { payloadInBytes = self.getSignableData(payload: payload!); } else { - let p = self.signatureStore.payloads[txIndex!]; + let p = self.payloads[txIndex!]; payloadInBytes = self.getSignableData(payload: p!); } var i = 0; while (i < pks.length) { - // Check if the public key is a registered signer - if (self.signatureStore.keyList[pks[i]] == nil){ - continue; + // check if the public key is a registered signer + if (self.keyList[pks[i]] == nil){ + return nil } let pk = PublicKey( publicKey: pks[i].decodeHex(), - signatureAlgorithm: SignatureAlgorithm(rawValue: self.signatureStore.keyList[pks[i]]!.sigAlgo) ?? panic ("invalid signature algo") + signatureAlgorithm: SignatureAlgorithm(rawValue: self.keyList[pks[i]]!.sigAlgo) ?? panic ("Invalid signature algo") ) keyList.add( pk, hashAlgorithm: HashAlgorithm.SHA3_256, - weight: self.signatureStore.keyList[pks[i]]!.weight + weight: self.keyList[pks[i]]!.weight ) - totalAuthorisedWeight = totalAuthorisedWeight + self.signatureStore.keyList[pks[i]]!.weight + totalAuthorisedWeight = totalAuthorisedWeight + self.keyList[pks[i]]!.weight i = i + 1; } @@ -269,10 +337,22 @@ pub contract OnChainMultiSig { } - - init(sigStore: SignatureStore) { - self.signatureStore = sigStore; - } + init(publicKeys: [String], pubKeyAttrs: [PubKeyAttr]){ + assert( publicKeys.length == pubKeyAttrs.length, message: "Public keys must have associated attributes") + self.payloads = {}; + self.payloadSigs = {}; + self.keyList = {}; + self.txIndex = 0; + var i: Int = 0; + while (i < publicKeys.length){ + self.keyList.insert(key: publicKeys[i], pubKeyAttrs[i]); + i = i + 1; + } + } + } + + pub fun createMultiSigManager(publicKeys: [String], pubKeyAttrs: [PubKeyAttr]): @Manager { + return <- create Manager(publicKeys: publicKeys, pubKeyAttrs: pubKeyAttrs) } } \ No newline at end of file diff --git a/lib/go/access-checks/access.go b/lib/go/access-checks/access.go new file mode 100644 index 0000000..e2350c9 --- /dev/null +++ b/lib/go/access-checks/access.go @@ -0,0 +1,110 @@ +package access + +import ( + "github.com/bjartek/go-with-the-flow/gwtf" + util "github.com/flow-hydraulics/onchain-multisig" +) + +func MultiSig_PubUpdateKeyList( + g *gwtf.GoWithTheFlow, + payerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/pubUpdateKeyList.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(payerAcct). + AccountArgument(vaultAcct). + Run() + events = util.ParseTestEvents(e) + return +} + +func MultiSig_PubUpdateTxIndex( + g *gwtf.GoWithTheFlow, + index uint64, + payerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/pubUpdateTxIndex.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(payerAcct). + AccountArgument(vaultAcct). + UInt64Argument(index). + Run() + events = util.ParseTestEvents(e) + return +} + +func MultiSig_PubUpdateStore( + g *gwtf.GoWithTheFlow, + index uint64, + payerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/pubUpdateStore.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(payerAcct). + AccountArgument(vaultAcct). + UInt64Argument(index). + Run() + events = util.ParseTestEvents(e) + return +} + +func MultiSig_OwnerUpdateKeyList( + g *gwtf.GoWithTheFlow, + payerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/ownerUpdateKeyList.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(payerAcct). + AccountArgument(vaultAcct). + Run() + events = util.ParseTestEvents(e) + return +} + +func MultiSig_OwnerUpdateTxIndex( + g *gwtf.GoWithTheFlow, + index uint64, + payerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/ownerUpdateTxIndex.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(payerAcct). + AccountArgument(vaultAcct). + UInt64Argument(index). + Run() + events = util.ParseTestEvents(e) + return +} + +func MultiSig_OwnerUpdateStore( + g *gwtf.GoWithTheFlow, + index uint64, + payerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/ownerUpdateStore.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(payerAcct). + AccountArgument(vaultAcct). + UInt64Argument(index). + Run() + events = util.ParseTestEvents(e) + return +} diff --git a/lib/go/access-checks/access_test.go b/lib/go/access-checks/access_test.go new file mode 100644 index 0000000..94900bd --- /dev/null +++ b/lib/go/access-checks/access_test.go @@ -0,0 +1,94 @@ +package access + +import ( + "testing" + + "github.com/bjartek/go-with-the-flow/gwtf" + util "github.com/flow-hydraulics/onchain-multisig" + "github.com/stretchr/testify/assert" +) + +func TestOwnerCannotUpdateStore(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + ownerAcct := "vaulted-account" + vaultAcct := "vaulted-account" + + _, err := MultiSig_PubUpdateStore(g, 11, ownerAcct, vaultAcct) + // error: cannot assign to `multiSigManager`: field has public access + // --> 237d2b40b5f6a90dd9ee3aa5c06af26c30a241eab3c75686cc72c5d198aca78f:8:10 + // | + // 8 | s.multiSigManager <-> store + // | ^^^^^^^^^^^^^^^ consider making it publicly settable with `pub(set)` + // + // error: cannot assign to constant member: `multiSigManager` + // --> 237d2b40b5f6a90dd9ee3aa5c06af26c30a241eab3c75686cc72c5d198aca78f:8:10 + // | + // 8 | s.multiSigManager <-> store + // | ^^^^^^^^^^^^^^^ + assert.Error(t, err) +} + +func TestPubCannotUpdateTxIndex(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + pubAcct := "w-1000" + vaultAcct := "vaulted-account" + + _, err := MultiSig_PubUpdateTxIndex(g, 11, pubAcct, vaultAcct) + // error: cannot assign to `txIndex`: field has public access + // --> 1f805903cd707281105ae12e6dc76e889b70a1fda5dd9a13dbbf707a920fe561:17:33 + // | + // 17 | vaultRef.multiSigManager.txIndex = txIndex + // | ^^^^^^^ consider making it publicly settable with `pub(set)` + assert.Error(t, err) +} + +func TestOwnerCannotUpdateTxIndex(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + ownerAcct := "vaulted-account" + vaultAcct := "vaulted-account" + + _, err := MultiSig_OwnerUpdateTxIndex(g, 11, ownerAcct, vaultAcct) + // error: cannot assign to `txIndex`: field has public access + // --> 1f805903cd707281105ae12e6dc76e889b70a1fda5dd9a13dbbf707a920fe561:17:33 + // | + // 17 | vaultRef.multiSigManager.txIndex = txIndex + // | ^^^^^^^ consider making it publicly settable with `pub(set)` + assert.Error(t, err) +} + +func TestPubCannotUpdateKeyList(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + pubAcct := "w-1000" + vaultAcct := "vaulted-account" + + _, err := MultiSig_PubUpdateKeyList(g, pubAcct, vaultAcct) + // error: cannot access `keyList`: field has private access + // --> a0a443291841b0ef697e410b6587d13d010cc39ebba9a085562a03624fe27886:18:8 + // | + //18 | vaultRef.multiSigManager.keyList.insert(key: "1aa4", pka) + // | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + assert.Error(t, err) + keys, err := util.GetStoreKeys(g, "vaulted-account") + assert.NoError(t, err) + assert.Len(t, keys, 5) +} + +// Owner must use the `addKeys` function in the Vault +func TestOwnerCannotUpdateKeyListDirectly(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + ownerAcct := "vaulted-account" + vaultAcct := "vaulted-account" + + _, err := MultiSig_OwnerUpdateKeyList(g, ownerAcct, vaultAcct) + + // error: cannot access `keyList`: field has private access + // --> a317001f16f9907ec9a948bf62b70db9469d10eb7f3d0598de982e8e8f73a60d:8:8 + // | + //8 | s.multiSigManager.keyList.insert(key: "1234", pka) + // | ^^^^^^^^^^^^^^^^^^^^^^^^^ + assert.Error(t, err) + + keys, err := util.GetStoreKeys(g, "vaulted-account") + assert.NoError(t, err) + assert.Len(t, keys, 5) +} diff --git a/lib/go/keys/keys_test.go b/lib/go/keys/keys_test.go index 0c435cf..60d5e9d 100644 --- a/lib/go/keys/keys_test.go +++ b/lib/go/keys/keys_test.go @@ -46,7 +46,11 @@ func TestRemovedKeyCannotAddSig(t *testing.T) { txIndex, err := util.GetTxIndex(g, vaultAcct) assert.NoError(t, err) - _, err = MultiSig_RemoveKey(g, removedAcct, txIndex, removedAcct, vaultAcct, false) + // Add a new payload to test new signature cannot be added by removed account + _, err = MultiSig_RemoveKey(g, vault.Acct500_1, txIndex+uint64(1), vault.Acct1000, vaultAcct, true) + assert.NoError(t, err) + + _, err = MultiSig_RemoveKey(g, removedAcct, txIndex+uint64(1), removedAcct, vaultAcct, false) assert.Error(t, err) } diff --git a/lib/go/scripts/deploy/deploy.go b/lib/go/scripts/deploy/deploy.go index 7696e33..cf7e6a3 100644 --- a/lib/go/scripts/deploy/deploy.go +++ b/lib/go/scripts/deploy/deploy.go @@ -9,7 +9,7 @@ import ( ) func main() { - // This relative path to flow.json is different in tests as it is the main package + // This relative path to flow.json is different in tests as it is the main package g := gwtf.NewGoWithTheFlow("../../flow.json") contractCode := util.ParseCadenceTemplate("../../contracts/MultiSigFlowToken.cdc") @@ -25,6 +25,9 @@ func main() { "w-250-2", "non-registered-account", ) + // The "owner" defined in flow.json is the owner of the contracts: + // - `MultiSigFlowToken` + // - `OnChainMultiSig` e, err := g.TransactionFromFile(txFilename, code). SignProposeAndPayAs("owner"). StringArgument("MultiSigFlowToken"). diff --git a/lib/go/test.sh b/lib/go/test.sh index 3bec0f7..be04863 100755 --- a/lib/go/test.sh +++ b/lib/go/test.sh @@ -32,7 +32,7 @@ if [ "${NETWORK}" == "emulator" ]; then SIGNER=emulator-account OWNER_PK="46acb0e0918e09a50fc2a6b12f14fc00822ad7dac6c6fd92427ec675b9745cbe5ae93d790e6fdd0683d7dd17b6156cc4201def8d6a992807796a5ce4a789005f" # we create the first account and transfer flow tokens to it - # the first account is the FiatToken owner + # the first account is the owner of the OnChainMultiSig contract flow accounts create --network="$NETWORK" --key="$OWNER_PK" --signer="$SIGNER" flow transactions send ./transactions/transfer_flow_tokens_emulator.cdc \ --arg=UFix64:100.0 \ @@ -50,4 +50,5 @@ go clean -testcache go run scripts/deploy/deploy.go go test ./vault -v +go test ./access-checks -v go test ./keys -v diff --git a/lib/go/vault/vault_test.go b/lib/go/vault/vault_test.go index 5202360..a9ca9b3 100644 --- a/lib/go/vault/vault_test.go +++ b/lib/go/vault/vault_test.go @@ -159,3 +159,35 @@ func TestExecutePayloadWithMultipleSig(t *testing.T) { assert.NoError(t, err) assert.Equal(t, transferAmount, (initFromBalance - postFromBalance).String()) } + +func TestSameAcctCannotAddMultipleSigPerTxIndex(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + transferAmount := "15.50000000" + transferTo := "owner" + + // + // First add a payload; total authorised weight is 500 + // + vaultAcct := "vaulted-account" + + initTxIndex, err := util.GetTxIndex(g, vaultAcct) + assert.NoError(t, err) + + _, err = MultiSig_Transfer(g, transferAmount, transferTo, initTxIndex+uint64(1), Acct500_1, vaultAcct, true) + assert.NoError(t, err) + + postTxIndex, err := util.GetTxIndex(g, vaultAcct) + assert.NoError(t, err) + assert.Equal(t, uint64(1), postTxIndex-initTxIndex) + + // + // Add another signature; total weight now is 500 + 250 + // + _, err = MultiSig_Transfer(g, transferAmount, transferTo, postTxIndex, Acct250_1, vaultAcct, false) + assert.NoError(t, err) + + // Same account cannot add signature again + _, err = MultiSig_Transfer(g, transferAmount, transferTo, postTxIndex, Acct250_1, vaultAcct, false) + assert.Error(t, err) + +} diff --git a/scripts/calc_signable_data.cdc b/scripts/calc_signable_data.cdc index 7b125ab..24786a7 100644 --- a/scripts/calc_signable_data.cdc +++ b/scripts/calc_signable_data.cdc @@ -1,4 +1,4 @@ -// This script reads the allowance field set in a vault for another resource +// This script calculate the signable bytes for each input value // Currently AnyStruct is input arg is not allowed, hence wrapping it in optional diff --git a/scripts/get_balance.cdc b/scripts/get_balance.cdc index 7528eb4..aa1276d 100644 --- a/scripts/get_balance.cdc +++ b/scripts/get_balance.cdc @@ -1,4 +1,4 @@ -// This script reads the balance field of an account's FiatToken Balance +// This script reads the balance field of an account's FlowToken Balance import FungibleToken from 0x{{.FungibleToken}} import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} diff --git a/scripts/get_block.cdc b/scripts/get_block.cdc deleted file mode 100644 index 8ededa1..0000000 --- a/scripts/get_block.cdc +++ /dev/null @@ -1,5 +0,0 @@ -pub fun main(): UInt64 { - let height = getCurrentBlock().height - return height -} - diff --git a/scripts/get_key_weight.cdc b/scripts/get_key_weight.cdc index d49888c..a1706ee 100644 --- a/scripts/get_key_weight.cdc +++ b/scripts/get_key_weight.cdc @@ -1,4 +1,4 @@ -// This script reads the balance field of an account's Balance +// This script gets the weight of a stored public key in a multiSigManager for a resource import FungibleToken from 0x{{.FungibleToken}} import OnChainMultiSig from 0x{{.OnChainMultiSig}} @@ -10,6 +10,6 @@ pub fun main(account: Address, key: String): UFix64 { .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() ?? panic("Could not borrow Pub Signer reference to the Vault") - let attr = vaultRef.signatureStore!.keyList[key]! + let attr = vaultRef.getSignerKeyAttr(publicKey: key)! return attr.weight } diff --git a/scripts/get_store_keys.cdc b/scripts/get_store_keys.cdc index de02ddc..1f10440 100644 --- a/scripts/get_store_keys.cdc +++ b/scripts/get_store_keys.cdc @@ -1,4 +1,4 @@ -// This script reads the balance field of an account's Balance +// This script gets all the stored public keys in a multiSigManager for a resource import FungibleToken from 0x{{.FungibleToken}} import OnChainMultiSig from 0x{{.OnChainMultiSig}} @@ -10,5 +10,5 @@ pub fun main(account: Address): [String] { .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() ?? panic("Could not borrow Pub Signer reference to the Vault") - return vaultRef.signatureStore!.keyList.keys + return vaultRef.getSignerKeys() } diff --git a/scripts/get_store_tx_index.cdc b/scripts/get_store_tx_index.cdc index 1d4f60b..32a90c2 100644 --- a/scripts/get_store_tx_index.cdc +++ b/scripts/get_store_tx_index.cdc @@ -1,6 +1,6 @@ -// This script reads the balance field of an account's FiatToken Balance +// This script gets the current TxIndex for payloads stored in multiSigManager in a resource +// The new payload must be this value + 1 -import FungibleToken from 0x{{.FungibleToken}} import OnChainMultiSig from 0x{{.OnChainMultiSig}} import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} @@ -10,5 +10,5 @@ pub fun main(account: Address): UInt64{ .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() ?? panic("Could not borrow Pub Signer reference to the Vault") - return vaultRef.signatureStore!.txIndex + return vaultRef.getTxIndex() } diff --git a/scripts/get_total_supply.cdc b/scripts/get_total_supply.cdc index 196981e..7fecff6 100644 --- a/scripts/get_total_supply.cdc +++ b/scripts/get_total_supply.cdc @@ -1,5 +1,4 @@ -// This script reads the total supply field -// of the FiatToken smart contract +// This script reads the total supply field of the MultiSigFlowToken smart contract import FiatToken from 0x{{.FiatToken}} diff --git a/scripts/get_vault_uuid.cdc b/scripts/get_vault_uuid.cdc index 4688e7d..b8fcc08 100644 --- a/scripts/get_vault_uuid.cdc +++ b/scripts/get_vault_uuid.cdc @@ -1,3 +1,5 @@ +// This script gets the uuid of the vault that owns the multiSigManager + import OnChainMultiSig from 0x{{.OnChainMultiSig}} import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} diff --git a/transactions/add_new_payload.cdc b/transactions/add_new_payload.cdc index 1ce359a..506a4be 100644 --- a/transactions/add_new_payload.cdc +++ b/transactions/add_new_payload.cdc @@ -1,4 +1,4 @@ -// Masterminter uses this to configure which minter the minter controller manages +// New payload to be added to multiSigManager for a resource import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} import OnChainMultiSig from 0x{{.OnChainMultiSig}} diff --git a/transactions/add_payload_signature.cdc b/transactions/add_payload_signature.cdc index b3dc80b..6e618b4 100644 --- a/transactions/add_payload_signature.cdc +++ b/transactions/add_payload_signature.cdc @@ -1,4 +1,4 @@ -// Masterminter uses this to configure which minter the minter controller manages +// New payload signature to be added to multiSigManager for a particular txIndex import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} import OnChainMultiSig from 0x{{.OnChainMultiSig}} diff --git a/transactions/create_vault.cdc b/transactions/create_vault.cdc index 91277ae..2641022 100644 --- a/transactions/create_vault.cdc +++ b/transactions/create_vault.cdc @@ -46,6 +46,7 @@ transaction(multiSigPubKeys: [String], multiSigKeyWeights: [UFix64]) { target: MultiSigFlowToken.VaultStoragePath ) + // The transaction that creates the vault can also add required multiSig public keys to the multiSigManager let s = signer.borrow<&MultiSigFlowToken.Vault>(from: MultiSigFlowToken.VaultStoragePath) ?? panic ("cannot borrow own resource") s.addKeys(multiSigPubKeys: multiSigPubKeys, multiSigKeyWeights: multiSigKeyWeights) } diff --git a/transactions/deploy_contract_with_auth.cdc b/transactions/deploy_contract_with_auth.cdc index 33efbd6..ed18316 100644 --- a/transactions/deploy_contract_with_auth.cdc +++ b/transactions/deploy_contract_with_auth.cdc @@ -1,4 +1,4 @@ -// This transactions deploys the FiatToken contract +// This transactions deploys the MultiSigFlowToken contract // // Owner of the contract has exclusive functions // We only provide the AuthAccount holder the owner resource diff --git a/transactions/executeTx.cdc b/transactions/executeTx.cdc index 177b461..211339e 100644 --- a/transactions/executeTx.cdc +++ b/transactions/executeTx.cdc @@ -1,4 +1,5 @@ -// Masterminter uses this to configure which minter the minter controller manages +// Attempt to execute a transaction with signatures for a txIndex stored in a multiSigManager for a resource + import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} import OnChainMultiSig from 0x{{.OnChainMultiSig}} diff --git a/transactions/ownerUpdateKeyList.cdc b/transactions/ownerUpdateKeyList.cdc new file mode 100644 index 0000000..fb6a832 --- /dev/null +++ b/transactions/ownerUpdateKeyList.cdc @@ -0,0 +1,12 @@ +// This tx attempts to directly modify keyList in a multiSigManager by the owner of the resource + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (multiSigVaultAddr: Address) { + prepare(owner: AuthAccount) { + let s = owner.borrow<&MultiSigFlowToken.Vault>(from: MultiSigFlowToken.VaultStoragePath) ?? panic ("cannot borrow own resource") + let pka = OnChainMultiSig.PubKeyAttr(sa: 1, w: 0.2) + s.multiSigManager.configureKeys(pks: ["1234"], kws: [0.2]) + } +} diff --git a/transactions/ownerUpdateStore.cdc b/transactions/ownerUpdateStore.cdc new file mode 100644 index 0000000..409c464 --- /dev/null +++ b/transactions/ownerUpdateStore.cdc @@ -0,0 +1,14 @@ +// This tx attempts to update the multiSigManager resource directly by the owner of the resource + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (multiSigVaultAddr: Address, txIndex: UInt64) { + prepare(owner: AuthAccount) { + let s = owner.borrow<&MultiSigFlowToken.Vault>(from: MultiSigFlowToken.VaultStoragePath) ?? panic ("cannot borrow own resource") + let store <- OnChainMultiSig.createMultiSigManager(publicKeys: [], pubKeyAttrs: []) + s.multiSigManager <-> store + destroy store + } + +} diff --git a/transactions/ownerUpdateTxIndex.cdc b/transactions/ownerUpdateTxIndex.cdc new file mode 100644 index 0000000..d4c1a71 --- /dev/null +++ b/transactions/ownerUpdateTxIndex.cdc @@ -0,0 +1,11 @@ +// This tx attempts to update the multiSigManager.txIndex resource directly by the owner of the resource + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (multiSigVaultAddr: Address, txIndex: UInt64) { + prepare(owner: AuthAccount) { + let s = owner.borrow<&MultiSigFlowToken.Vault>(from: MultiSigFlowToken.VaultStoragePath) ?? panic ("cannot borrow own resource") + s.multiSigManager.txIndex = txIndex + } +} diff --git a/transactions/pubUpdateKeyList.cdc b/transactions/pubUpdateKeyList.cdc new file mode 100644 index 0000000..5ef2043 --- /dev/null +++ b/transactions/pubUpdateKeyList.cdc @@ -0,0 +1,20 @@ +// This tx attempts to directly modify keyList in a multiSigManager by a public account + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (multiSigVaultAddr: Address) { + prepare(payer: AuthAccount) { + } + + execute { + // Get the account of where the multisig vault is + let acct = getAccount(multiSigVaultAddr) + + let vaultRef = acct.getCapability(MultiSigFlowToken.VaultPubSigner) + .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() + ?? panic("Could not borrow vault pub sig reference") + + vaultRef.multiSigManager.configureKeys(pks: ["1234"], kws: [0.2]) + } +} diff --git a/transactions/pubUpdateStore.cdc b/transactions/pubUpdateStore.cdc new file mode 100644 index 0000000..376df5d --- /dev/null +++ b/transactions/pubUpdateStore.cdc @@ -0,0 +1,22 @@ +// This tx attempts to update the multiSigManager resource directly by a public account + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (multiSigVaultAddr: Address, txIndex: UInt64) { + prepare(payer: AuthAccount) { + } + + execute { + // Get the account of where the multisig vault is + let acct = getAccount(multiSigVaultAddr) + + let vaultRef = acct.getCapability(MultiSigFlowToken.VaultPubSigner) + .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() + ?? panic("Could not borrow vault pub sig reference") + + let store <- OnChainMultiSig.createMultiSigManager(publicKeys: [], pubKeyAttrs: []) + vaultRef.multiSigManager <-> store + destroy store + } +} diff --git a/transactions/pubUpdateTxIndex.cdc b/transactions/pubUpdateTxIndex.cdc new file mode 100644 index 0000000..137156b --- /dev/null +++ b/transactions/pubUpdateTxIndex.cdc @@ -0,0 +1,20 @@ +// This tx attempts to update the multiSigManager.txIndex resource directly by a public account + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (multiSigVaultAddr: Address, txIndex: UInt64) { + prepare(payer: AuthAccount) { + } + + execute { + // Get the account of where the multisig vault is + let acct = getAccount(multiSigVaultAddr) + + let vaultRef = acct.getCapability(MultiSigFlowToken.VaultPubSigner) + .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() + ?? panic("Could not borrow vault pub sig reference") + + vaultRef.multiSigManager.txIndex = txIndex + } +}