diff --git a/contracts/MultiSigFlowToken.cdc b/contracts/MultiSigFlowToken.cdc index 6122103..2331483 100644 --- a/contracts/MultiSigFlowToken.cdc +++ b/contracts/MultiSigFlowToken.cdc @@ -79,14 +79,14 @@ pub contract MultiSigFlowToken: FungibleToken { let manager = OnChainMultiSig.Manager(sigStore: self.signatureStore); let p = manager.readyForExecution(txIndex: txIndex) ?? panic ("TX not ready for execution") switch p.method { - case "configureKeys": - let pubKey = p.args[0].value as? [String] ?? panic ("cannot downcast public key"); - let weight = p.args[1].value as? [UFix64] ?? panic ("cannot downcast weight"); - let newSignatureStore = manager.configureKeys(pks: pubKey, kws: weight) + case "configureKey": + let pubKey = p.args[0].value as? String ?? panic ("cannot downcast public key"); + let weight = p.args[1].value as? UFix64 ?? panic ("cannot downcast weight"); + let newSignatureStore = manager.configureKeys(pks: [pubKey], kws: [weight]) self.signatureStore = newSignatureStore; - case "removeKeys": - let pubKey = p.args[0].value as? [String] ?? panic ("cannot downcast public key"); - let newSignatureStore = manager.removeKeys(pks: pubKey) + case "removeKey": + let pubKey = p.args[0].value as? String ?? panic ("cannot downcast public key"); + let newSignatureStore = manager.removeKeys(pks: [pubKey]) self.signatureStore = newSignatureStore; case "withdraw": let amount = p.args[0].value as? UFix64 ?? panic ("cannot downcast amount"); diff --git a/lib/go/keys/keys.go b/lib/go/keys/keys.go new file mode 100644 index 0000000..df1fa34 --- /dev/null +++ b/lib/go/keys/keys.go @@ -0,0 +1,103 @@ +package keys + +import ( + "github.com/bjartek/go-with-the-flow/gwtf" + util "github.com/flow-hydraulics/onchain-multisig" + "github.com/onflow/cadence" +) + +func MultiSig_NewRemoveSignerPayload( + g *gwtf.GoWithTheFlow, + acctToRemove string, + signerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/new_pending_remove_multisig_key.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + method := "removeKey" + pkToRemove := g.Accounts[acctToRemove].PrivateKey.PublicKey().String() + signable, err := util.GetSignableDataFromScript(g, method, cadence.NewString(pkToRemove[2:])) + if err != nil { + return + } + + sig, err := util.SignPayloadOffline(g, signable, signerAcct) + if err != nil { + return + } + + signerPubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String() + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(signerAcct). + StringArgument(signerPubKey[2:]). + StringArgument(sig). + AccountArgument(vaultAcct). + StringArgument(method). + StringArgument(pkToRemove[2:]). + Run() + events = util.ParseTestEvents(e) + return +} + +func MultiSig_NewRemoveKeyPayloadSignature( + g *gwtf.GoWithTheFlow, + acctToRemove string, + txIndex uint64, + signerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + method := "removeKey" + pkToRemove := g.Accounts[acctToRemove].PrivateKey.PublicKey().String() + signable, err := util.GetSignableDataFromScript(g, method, cadence.NewString(pkToRemove[2:])) + if err != nil { + return + } + + sig, err := util.SignPayloadOffline(g, signable, signerAcct) + if err != nil { + return + } + + return util.MultiSigVault_NewPayloadSignature(g, txIndex, sig, signerAcct, vaultAcct) +} + +func MultiSig_NewConfigSignerPayload( + g *gwtf.GoWithTheFlow, + acctToConfig string, + acctToConfigWeight string, + signerAcct string, + vaultAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/new_pending_config_multisig_key.cdc" + txScript := util.ParseCadenceTemplate(txFilename) + + method := "configureKey" + pkToConfig := g.Accounts[acctToConfig].PrivateKey.PublicKey().String() + weightToConfig, err := cadence.NewUFix64(acctToConfigWeight) + if err != nil { + return + } + signable, err := util.GetSignableDataFromScript(g, method, cadence.NewString(pkToConfig[2:]), weightToConfig) + if err != nil { + return + } + + sig, err := util.SignPayloadOffline(g, signable, signerAcct) + if err != nil { + return + } + + signerPubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String() + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(signerAcct). + StringArgument(signerPubKey[2:]). + StringArgument(sig). + AccountArgument(vaultAcct). + StringArgument(method). + StringArgument(pkToConfig[2:]). + UFix64Argument(acctToConfigWeight). + Run() + events = util.ParseTestEvents(e) + return +} diff --git a/lib/go/keys/keys_test.go b/lib/go/keys/keys_test.go new file mode 100644 index 0000000..0ccc923 --- /dev/null +++ b/lib/go/keys/keys_test.go @@ -0,0 +1,84 @@ +package keys + +import ( + "strconv" + "testing" + + "github.com/bjartek/go-with-the-flow/gwtf" + util "github.com/flow-hydraulics/onchain-multisig" + "github.com/flow-hydraulics/onchain-multisig/vault" + "github.com/stretchr/testify/assert" +) + +func TestAddNewPendingKeyRemoval(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + + vaultAcct := "vaulted-account" + payerAcct := "owner" + + initTxIndex, err := util.GetTxIndex(g, vaultAcct) + assert.NoError(t, err) + + events, err := MultiSig_NewRemoveSignerPayload(g, vault.Acct500_1, vault.Acct1000, vaultAcct) + assert.NoError(t, err) + + postTxIndex, err := util.GetTxIndex(g, vaultAcct) + assert.NoError(t, err) + assert.Equal(t, uint64(1), postTxIndex-initTxIndex) + + uuid, err := util.GetVaultUUID(g, vaultAcct) + assert.NoError(t, err) + + util.NewExpectedEvent("OnChainMultiSig", "NewPayloadAdded"). + AddField("resourceId", strconv.Itoa(int(uuid))). + AddField("txIndex", strconv.Itoa(int(postTxIndex))). + AssertEqual(t, events[0]) + + _, err = vault.MultiSig_VaultExecuteTx(g, postTxIndex, payerAcct, vaultAcct) + assert.NoError(t, err) + + removedPk := g.Accounts[vault.Acct500_1].PrivateKey.PublicKey().String()[2:] + keys, err := util.GetStoreKeys(g, vaultAcct) + var removed bool = true + for _, key := range keys { + if key == removedPk { + removed = false + } + } + assert.Equal(t, removed, true) +} + +func TestRemovaledKeyCannotAddSig(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + + vaultAcct := "vaulted-account" + removedAcct := vault.Acct500_1 + + _, err := MultiSig_NewRemoveKeyPayloadSignature(g, removedAcct, 1, removedAcct, vaultAcct) + assert.Error(t, err) +} + +func TestAddNewPendingKeyConfig(t *testing.T) { + g := gwtf.NewGoWithTheFlow("../../../flow.json") + + vaultAcct := "vaulted-account" + payerAcct := "owner" + newAcct := vault.Acct500_1 + newAcctWeight := "100.00000000" + + initTxIndex, err := util.GetTxIndex(g, vaultAcct) + assert.NoError(t, err) + + _, err = MultiSig_NewConfigSignerPayload(g, newAcct, newAcctWeight, vault.Acct1000, vaultAcct) + assert.NoError(t, err) + + postTxIndex, err := util.GetTxIndex(g, vaultAcct) + assert.NoError(t, err) + assert.Equal(t, uint64(1), postTxIndex-initTxIndex) + + _, err = vault.MultiSig_VaultExecuteTx(g, postTxIndex, payerAcct, vaultAcct) + assert.NoError(t, err) + + weight, err := util.GetKeyWeight(g, vaultAcct, newAcct) + assert.Equal(t, newAcctWeight, weight.String()) +} diff --git a/lib/go/test.sh b/lib/go/test.sh index 8c7dc4d..3bec0f7 100755 --- a/lib/go/test.sh +++ b/lib/go/test.sh @@ -50,3 +50,4 @@ go clean -testcache go run scripts/deploy/deploy.go go test ./vault -v +go test ./keys -v diff --git a/lib/go/util.go b/lib/go/util.go index d7ffea9..0f6ab82 100644 --- a/lib/go/util.go +++ b/lib/go/util.go @@ -130,6 +130,21 @@ func GetStoreKeys(g *gwtf.GoWithTheFlow, account string) (result []string, err e return } +func GetKeyWeight(g *gwtf.GoWithTheFlow, resourceAcct string, signerAcct string) (result cadence.UFix64, err error) { + filename := "../../../scripts/get_key_weight.cdc" + script := ParseCadenceTemplate(filename) + signerPubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String()[2:] + value, err := g.ScriptFromFile(filename, script). + AccountArgument(resourceAcct). + StringArgument(signerPubKey). + RunReturns() + if err != nil { + return + } + result = value.(cadence.UFix64) + return +} + func GetTxIndex(g *gwtf.GoWithTheFlow, account string) (result uint64, err error) { filename := "../../../scripts/get_store_tx_index.cdc" script := ParseCadenceTemplate(filename) @@ -213,3 +228,25 @@ func GetSignableDataFromScript( } return } + +func MultiSigVault_NewPayloadSignature( + g *gwtf.GoWithTheFlow, + txIndex uint64, + sig string, + signerAcct string, + resourceAcct string, +) (events []*gwtf.FormatedEvent, err error) { + txFilename := "../../../transactions/add_payload_signature.cdc" + txScript := ParseCadenceTemplate(txFilename) + + signerPubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String() + e, err := g.TransactionFromFile(txFilename, txScript). + SignProposeAndPayAs(signerAcct). + UInt64Argument(txIndex). + StringArgument(signerPubKey[2:]). + StringArgument(sig). + AccountArgument(resourceAcct). + Run() + events = ParseTestEvents(e) + return +} diff --git a/lib/go/vault/vault.go b/lib/go/vault/vault.go index 41db2bf..a124f06 100644 --- a/lib/go/vault/vault.go +++ b/lib/go/vault/vault.go @@ -91,11 +91,10 @@ func MultiSig_NewPendingTransferPayload( return } - pubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String() - + signerPubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String() e, err := g.TransactionFromFile(txFilename, txScript). SignProposeAndPayAs(signerAcct). - StringArgument(pubKey). + StringArgument(signerPubKey[2:]). StringArgument(sig). AccountArgument(vaultAcct). StringArgument(method). @@ -114,9 +113,6 @@ func MultiSig_NewPayloadSignature( signerAcct string, vaultAcct string, ) (events []*gwtf.FormatedEvent, err error) { - txFilename := "../../../transactions/add_payload_signature.cdc" - txScript := util.ParseCadenceTemplate(txFilename) - method := "transfer" ufix64, err := cadence.NewUFix64(amount) if err != nil { @@ -133,29 +129,21 @@ func MultiSig_NewPayloadSignature( return } - pubKey := g.Accounts[signerAcct].PrivateKey.PublicKey().String() - e, err := g.TransactionFromFile(txFilename, txScript). - SignProposeAndPayAs(signerAcct). - UInt64Argument(txIndex). - StringArgument(pubKey). - StringArgument(sig). - AccountArgument(vaultAcct). - Run() - events = util.ParseTestEvents(e) - return + return util.MultiSigVault_NewPayloadSignature(g, txIndex, sig, signerAcct, vaultAcct) } func MultiSig_VaultExecuteTx( g *gwtf.GoWithTheFlow, index uint64, payerAcct string, + vaultAcct string, ) (events []*gwtf.FormatedEvent, err error) { txFilename := "../../../transactions/executeTx.cdc" txScript := util.ParseCadenceTemplate(txFilename) e, err := g.TransactionFromFile(txFilename, txScript). SignProposeAndPayAs(payerAcct). - AccountArgument("vaulted-account"). + AccountArgument(vaultAcct). UInt64Argument(index). Run() events = util.ParseTestEvents(e) diff --git a/lib/go/vault/vault_test.go b/lib/go/vault/vault_test.go index 42de035..573b046 100644 --- a/lib/go/vault/vault_test.go +++ b/lib/go/vault/vault_test.go @@ -85,15 +85,16 @@ func TestExecutePendingTransnferFromFullAcct(t *testing.T) { g := gwtf.NewGoWithTheFlow("../../../flow.json") transferAmount := "15.50000000" payerAcct := "owner" + vaultAcct := "vaulted-account" txIndex := uint64(1) - initFromBalance, err := util.GetBalance(g, "vaulted-account") + initFromBalance, err := util.GetBalance(g, vaultAcct) assert.NoError(t, err) - _, err = MultiSig_VaultExecuteTx(g, txIndex, payerAcct) + _, err = MultiSig_VaultExecuteTx(g, txIndex, payerAcct, vaultAcct) assert.NoError(t, err) - postFromBalance, err := util.GetBalance(g, "vaulted-account") + postFromBalance, err := util.GetBalance(g, vaultAcct) assert.NoError(t, err) assert.Equal(t, transferAmount, (initFromBalance - postFromBalance).String()) @@ -134,7 +135,7 @@ func TestExecutePayloadWithMultipleSig(t *testing.T) { AssertEqual(t, events[0]) // This should fail because the weight is less than 1000 - _, err = MultiSig_VaultExecuteTx(g, postTxIndex, payerAcct) + _, err = MultiSig_VaultExecuteTx(g, postTxIndex, payerAcct, vaultAcct) assert.Error(t, err) // @@ -146,7 +147,7 @@ func TestExecutePayloadWithMultipleSig(t *testing.T) { initFromBalance, err := util.GetBalance(g, "vaulted-account") assert.NoError(t, err) - _, err = MultiSig_VaultExecuteTx(g, postTxIndex, payerAcct) + _, err = MultiSig_VaultExecuteTx(g, postTxIndex, payerAcct, vaultAcct) assert.NoError(t, err) postFromBalance, err := util.GetBalance(g, "vaulted-account") diff --git a/scripts/get_key_weight.cdc b/scripts/get_key_weight.cdc new file mode 100644 index 0000000..d49888c --- /dev/null +++ b/scripts/get_key_weight.cdc @@ -0,0 +1,15 @@ +// This script reads the balance field of an account's Balance + +import FungibleToken from 0x{{.FungibleToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} + +pub fun main(account: Address, key: String): UFix64 { + let acct = getAccount(account) + let vaultRef = acct.getCapability(MultiSigFlowToken.VaultPubSigner) + .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() + ?? panic("Could not borrow Pub Signer reference to the Vault") + + let attr = vaultRef.signatureStore!.keyList[key]! + return attr.weight +} diff --git a/transactions/new_pending_config_multisig_key.cdc b/transactions/new_pending_config_multisig_key.cdc new file mode 100644 index 0000000..760a9bc --- /dev/null +++ b/transactions/new_pending_config_multisig_key.cdc @@ -0,0 +1,22 @@ +// Masterminter uses this to configure which minter the minter controller manages + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (publicKey: String, sig: String, addr: Address, method: String, pubKeyToConfig: String, weight: UFix64) { + prepare(oneOfMultiSig: AuthAccount) { + } + + execute { + let vaultedAcct = getAccount(addr) + + let pubSigRef = vaultedAcct.getCapability(MultiSigFlowToken.VaultPubSigner) + .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() + ?? panic("Could not borrow vault pub sig reference") + + let pk = OnChainMultiSig.PayloadArg(t: Type(), v: pubKeyToConfig); + let w = OnChainMultiSig.PayloadArg(t: Type(), v: weight); + let p = OnChainMultiSig.PayloadDetails(method: method, args: [pk, w]); + return pubSigRef.addNewPayload(payload: p, publicKey: publicKey, sig: sig.decodeHex()) + } +} diff --git a/transactions/new_pending_remove_multisig_key.cdc b/transactions/new_pending_remove_multisig_key.cdc new file mode 100644 index 0000000..e480a54 --- /dev/null +++ b/transactions/new_pending_remove_multisig_key.cdc @@ -0,0 +1,21 @@ +// Masterminter uses this to configure which minter the minter controller manages + +import MultiSigFlowToken from 0x{{.MultiSigFlowToken}} +import OnChainMultiSig from 0x{{.OnChainMultiSig}} + +transaction (publicKey: String, sig: String, addr: Address, method: String, pubKeyToRemove: String) { + prepare(oneOfMultiSig: AuthAccount) { + } + + execute { + let vaultedAcct = getAccount(addr) + + let pubSigRef = vaultedAcct.getCapability(MultiSigFlowToken.VaultPubSigner) + .borrow<&MultiSigFlowToken.Vault{OnChainMultiSig.PublicSigner}>() + ?? panic("Could not borrow vault pub sig reference") + + let pk = OnChainMultiSig.PayloadArg(t: Type(), v: pubKeyToRemove); + let p = OnChainMultiSig.PayloadDetails(method: method, args: [pk]); + return pubSigRef.addNewPayload(payload: p, publicKey: publicKey, sig: sig.decodeHex()) + } +}