From 00332620a6e4cf8cc8beed69e2f90ec4d0d57242 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Mon, 30 Mar 2020 17:46:18 -0400 Subject: [PATCH] Implemented Properties --- genesis/genesis.go | 3 + node/node.go | 2 + vms/propertyfx/burn_operation.go | 14 + vms/propertyfx/burn_operation_test.go | 23 ++ vms/propertyfx/credential.go | 10 + vms/propertyfx/factory.go | 16 + vms/propertyfx/factory_test.go | 12 + vms/propertyfx/fx.go | 109 ++++++ vms/propertyfx/fx_test.go | 473 ++++++++++++++++++++++++++ vms/propertyfx/mint_operation.go | 37 ++ vms/propertyfx/mint_operation_test.go | 34 ++ vms/propertyfx/mint_output.go | 10 + vms/propertyfx/owned_output.go | 10 + 13 files changed, 753 insertions(+) create mode 100644 vms/propertyfx/burn_operation.go create mode 100644 vms/propertyfx/burn_operation_test.go create mode 100644 vms/propertyfx/credential.go create mode 100644 vms/propertyfx/factory.go create mode 100644 vms/propertyfx/factory_test.go create mode 100644 vms/propertyfx/fx.go create mode 100644 vms/propertyfx/fx_test.go create mode 100644 vms/propertyfx/mint_operation.go create mode 100644 vms/propertyfx/mint_operation_test.go create mode 100644 vms/propertyfx/mint_output.go create mode 100644 vms/propertyfx/owned_output.go diff --git a/genesis/genesis.go b/genesis/genesis.go index d86a275..b71b737 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/gecko/vms/evm" "github.com/ava-labs/gecko/vms/nftfx" "github.com/ava-labs/gecko/vms/platformvm" + "github.com/ava-labs/gecko/vms/propertyfx" "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/gecko/vms/spchainvm" "github.com/ava-labs/gecko/vms/spdagvm" @@ -160,6 +161,7 @@ func Aliases(networkID uint32) (generalAliases map[string][]string, chainAliases timestampvm.ID.Key(): []string{"timestamp"}, secp256k1fx.ID.Key(): []string{"secp256k1fx"}, nftfx.ID.Key(): []string{"nftfx"}, + propertyfx.ID.Key(): []string{"propertyfx"}, } genesisBytes := Genesis(networkID) @@ -353,6 +355,7 @@ func Genesis(networkID uint32) []byte { FxIDs: []ids.ID{ secp256k1fx.ID, nftfx.ID, + propertyfx.ID, }, Name: "X-Chain", }, diff --git a/node/node.go b/node/node.go index 45db23e..331389c 100644 --- a/node/node.go +++ b/node/node.go @@ -41,6 +41,7 @@ import ( "github.com/ava-labs/gecko/vms/evm" "github.com/ava-labs/gecko/vms/nftfx" "github.com/ava-labs/gecko/vms/platformvm" + "github.com/ava-labs/gecko/vms/propertyfx" "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/gecko/vms/spchainvm" "github.com/ava-labs/gecko/vms/spdagvm" @@ -337,6 +338,7 @@ func (n *Node) initVMManager() { n.vmManager.RegisterVMFactory(timestampvm.ID, ×tampvm.Factory{}) n.vmManager.RegisterVMFactory(secp256k1fx.ID, &secp256k1fx.Factory{}) n.vmManager.RegisterVMFactory(nftfx.ID, &nftfx.Factory{}) + n.vmManager.RegisterVMFactory(propertyfx.ID, &propertyfx.Factory{}) } // Create the EventDispatcher used for hooking events diff --git a/vms/propertyfx/burn_operation.go b/vms/propertyfx/burn_operation.go new file mode 100644 index 0000000..c662f6e --- /dev/null +++ b/vms/propertyfx/burn_operation.go @@ -0,0 +1,14 @@ +package propertyfx + +import ( + "github.com/ava-labs/gecko/vms/components/verify" + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +// BurnOperation ... +type BurnOperation struct { + secp256k1fx.Input `serialize:"true"` +} + +// Outs ... +func (op *BurnOperation) Outs() []verify.Verifiable { return nil } diff --git a/vms/propertyfx/burn_operation_test.go b/vms/propertyfx/burn_operation_test.go new file mode 100644 index 0000000..1e74833 --- /dev/null +++ b/vms/propertyfx/burn_operation_test.go @@ -0,0 +1,23 @@ +package propertyfx + +import ( + "testing" + + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +func TestBurnOperationInvalid(t *testing.T) { + op := BurnOperation{Input: secp256k1fx.Input{ + SigIndices: []uint32{1, 0}, + }} + if err := op.Verify(); err == nil { + t.Fatalf("operation should have failed verification") + } +} + +func TestBurnOperationNumberOfOutput(t *testing.T) { + op := BurnOperation{} + if outs := op.Outs(); len(outs) != 0 { + t.Fatalf("wrong number of outputs") + } +} diff --git a/vms/propertyfx/credential.go b/vms/propertyfx/credential.go new file mode 100644 index 0000000..0b468cf --- /dev/null +++ b/vms/propertyfx/credential.go @@ -0,0 +1,10 @@ +package propertyfx + +import ( + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +// Credential ... +type Credential struct { + secp256k1fx.Credential `serialize:"true"` +} diff --git a/vms/propertyfx/factory.go b/vms/propertyfx/factory.go new file mode 100644 index 0000000..6cb6fba --- /dev/null +++ b/vms/propertyfx/factory.go @@ -0,0 +1,16 @@ +package propertyfx + +import ( + "github.com/ava-labs/gecko/ids" +) + +// ID that this Fx uses when labeled +var ( + ID = ids.NewID([32]byte{'p', 'r', 'o', 'p', 'e', 'r', 't', 'y', 'f', 'x'}) +) + +// Factory ... +type Factory struct{} + +// New ... +func (f *Factory) New() interface{} { return &Fx{} } diff --git a/vms/propertyfx/factory_test.go b/vms/propertyfx/factory_test.go new file mode 100644 index 0000000..a22fdd2 --- /dev/null +++ b/vms/propertyfx/factory_test.go @@ -0,0 +1,12 @@ +package propertyfx + +import ( + "testing" +) + +func TestFactory(t *testing.T) { + factory := Factory{} + if fx := factory.New(); fx == nil { + t.Fatalf("Factory.New returned nil") + } +} diff --git a/vms/propertyfx/fx.go b/vms/propertyfx/fx.go new file mode 100644 index 0000000..41cd225 --- /dev/null +++ b/vms/propertyfx/fx.go @@ -0,0 +1,109 @@ +package propertyfx + +import ( + "errors" + + "github.com/ava-labs/gecko/utils/wrappers" + "github.com/ava-labs/gecko/vms/components/verify" + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +var ( + errWrongTxType = errors.New("wrong tx type") + errWrongUTXOType = errors.New("wrong utxo type") + errWrongOperationType = errors.New("wrong operation type") + errWrongCredentialType = errors.New("wrong credential type") + + errNoUTXOs = errors.New("an operation must consume at least one UTXO") + errWrongNumberOfUTXOs = errors.New("wrong number of UTXOs for the operation") + errWrongNumberOfCreds = errors.New("wrong number of credentials for the operation") + + errWrongMintOutput = errors.New("wrong mint output provided") + + errCantTransfer = errors.New("cant transfer with this fx") +) + +// Fx ... +type Fx struct{ secp256k1fx.Fx } + +// Initialize ... +func (fx *Fx) Initialize(vmIntf interface{}) error { + if err := fx.InitializeVM(vmIntf); err != nil { + return err + } + + log := fx.VM.Logger() + log.Debug("Initializing nft fx") + + c := fx.VM.Codec() + errs := wrappers.Errs{} + errs.Add( + c.RegisterType(&MintOutput{}), + c.RegisterType(&OwnedOutput{}), + c.RegisterType(&MintOperation{}), + c.RegisterType(&BurnOperation{}), + c.RegisterType(&Credential{}), + ) + return errs.Err +} + +// VerifyOperation ... +func (fx *Fx) VerifyOperation(txIntf, opIntf, credIntf interface{}, utxosIntf []interface{}) error { + tx, ok := txIntf.(secp256k1fx.Tx) + switch { + case !ok: + return errWrongTxType + case len(utxosIntf) != 1: + return errWrongNumberOfUTXOs + } + + cred, ok := credIntf.(*Credential) + if !ok { + return errWrongCredentialType + } + + switch op := opIntf.(type) { + case *MintOperation: + return fx.VerifyMintOperation(tx, op, cred, utxosIntf[0]) + case *BurnOperation: + return fx.VerifyTransferOperation(tx, op, cred, utxosIntf[0]) + default: + return errWrongOperationType + } +} + +// VerifyMintOperation ... +func (fx *Fx) VerifyMintOperation(tx secp256k1fx.Tx, op *MintOperation, cred *Credential, utxoIntf interface{}) error { + out, ok := utxoIntf.(*MintOutput) + if !ok { + return errWrongUTXOType + } + + if err := verify.All(op, cred, out); err != nil { + return err + } + + switch { + case !out.OutputOwners.Equals(&op.MintOutput.OutputOwners): + return errWrongMintOutput + default: + return fx.Fx.VerifyCredentials(tx, &op.MintInput, &cred.Credential, &out.OutputOwners) + } +} + +// VerifyTransferOperation ... +func (fx *Fx) VerifyTransferOperation(tx secp256k1fx.Tx, op *BurnOperation, cred *Credential, utxoIntf interface{}) error { + out, ok := utxoIntf.(*OwnedOutput) + if !ok { + return errWrongUTXOType + } + + if err := verify.All(op, cred, out); err != nil { + return err + } + + return fx.VerifyCredentials(tx, &op.Input, &cred.Credential, &out.OutputOwners) +} + +// VerifyTransfer ... +func (fx *Fx) VerifyTransfer(_, _, _, _ interface{}) error { return errCantTransfer } diff --git a/vms/propertyfx/fx_test.go b/vms/propertyfx/fx_test.go new file mode 100644 index 0000000..cfdf5c9 --- /dev/null +++ b/vms/propertyfx/fx_test.go @@ -0,0 +1,473 @@ +package propertyfx + +import ( + "testing" + "time" + + "github.com/ava-labs/gecko/ids" + "github.com/ava-labs/gecko/utils/crypto" + "github.com/ava-labs/gecko/utils/hashing" + "github.com/ava-labs/gecko/utils/logging" + "github.com/ava-labs/gecko/utils/timer" + "github.com/ava-labs/gecko/vms/components/codec" + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +var ( + txBytes = []byte{0, 1, 2, 3, 4, 5} + sigBytes = [crypto.SECP256K1RSigLen]byte{ + 0x0e, 0x33, 0x4e, 0xbc, 0x67, 0xa7, 0x3f, 0xe8, + 0x24, 0x33, 0xac, 0xa3, 0x47, 0x88, 0xa6, 0x3d, + 0x58, 0xe5, 0x8e, 0xf0, 0x3a, 0xd5, 0x84, 0xf1, + 0xbc, 0xa3, 0xb2, 0xd2, 0x5d, 0x51, 0xd6, 0x9b, + 0x0f, 0x28, 0x5d, 0xcd, 0x3f, 0x71, 0x17, 0x0a, + 0xf9, 0xbf, 0x2d, 0xb1, 0x10, 0x26, 0x5c, 0xe9, + 0xdc, 0xc3, 0x9d, 0x7a, 0x01, 0x50, 0x9d, 0xe8, + 0x35, 0xbd, 0xcb, 0x29, 0x3a, 0xd1, 0x49, 0x32, + 0x00, + } + addrBytes = [hashing.AddrLen]byte{ + 0x01, 0x5c, 0xce, 0x6c, 0x55, 0xd6, 0xb5, 0x09, + 0x84, 0x5c, 0x8c, 0x4e, 0x30, 0xbe, 0xd9, 0x8d, + 0x39, 0x1a, 0xe7, 0xf0, + } +) + +func TestFxInitialize(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + fx := Fx{} + err := fx.Initialize(&vm) + if err != nil { + t.Fatal(err) + } +} + +func TestFxInitializeInvalid(t *testing.T) { + fx := Fx{} + err := fx.Initialize(nil) + if err == nil { + t.Fatalf("Should have returned an error") + } +} + +func TestFxVerifyMintOperation(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &MintOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + MintOutput: MintOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }}, + } + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, op, cred, utxos); err != nil { + t.Fatal(err) + } +} + +func TestFxVerifyMintOperationWrongTx(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &MintOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + } + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(nil, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an invalid tx") + } +} + +func TestFxVerifyMintOperationWrongNumberUTXOs(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + } + + utxos := []interface{}{} + if err := fx.VerifyOperation(tx, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to not enough utxos") + } +} + +func TestFxVerifyMintOperationWrongCredential(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + utxo := &MintOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + } + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, op, nil, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to a bad credential") + } +} + +func TestFxVerifyMintOperationInvalidUTXO(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + } + + utxos := []interface{}{nil} + if err := fx.VerifyOperation(tx, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an invalid utxo") + } +} + +func TestFxVerifyMintOperationFailingVerification(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &MintOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + ids.ShortEmpty, + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + } + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an invalid utxo output") + } +} + +func TestFxVerifyMintOperationInvalidGroupID(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &MintOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + op := &MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + } + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an invalid mint output") + } +} + +func TestFxVerifyTransferOperation(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &OwnedOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + op := &BurnOperation{Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }} + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, op, cred, utxos); err != nil { + t.Fatal(err) + } +} + +func TestFxVerifyTransferOperationWrongUTXO(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + op := &BurnOperation{Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }} + + utxos := []interface{}{nil} + if err := fx.VerifyOperation(tx, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an invalid utxo") + } +} + +func TestFxVerifyTransferOperationFailedVerify(t *testing.T) { + + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &OwnedOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + op := &BurnOperation{Input: secp256k1fx.Input{ + SigIndices: []uint32{1, 0}, + }} + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, op, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an invalid utxo output") + } +} + +func TestFxVerifyOperationUnknownOperation(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + tx := &secp256k1fx.TestTx{ + Bytes: txBytes, + } + cred := &Credential{Credential: secp256k1fx.Credential{ + Sigs: [][crypto.SECP256K1RSigLen]byte{ + sigBytes, + }, + }} + utxo := &OwnedOutput{OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + ids.NewShortID(addrBytes), + }, + }} + + utxos := []interface{}{utxo} + if err := fx.VerifyOperation(tx, nil, cred, utxos); err == nil { + t.Fatalf("VerifyOperation should have errored due to an unknown operation") + } +} + +func TestFxVerifyTransfer(t *testing.T) { + vm := secp256k1fx.TestVM{ + CLK: new(timer.Clock), + Code: codec.NewDefault(), + Log: logging.NoLog{}, + } + date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) + vm.CLK.Set(date) + + fx := Fx{} + if err := fx.Initialize(&vm); err != nil { + t.Fatal(err) + } + if err := fx.VerifyTransfer(nil, nil, nil, nil); err == nil { + t.Fatalf("this Fx doesn't support transfers") + } +} diff --git a/vms/propertyfx/mint_operation.go b/vms/propertyfx/mint_operation.go new file mode 100644 index 0000000..01fd31a --- /dev/null +++ b/vms/propertyfx/mint_operation.go @@ -0,0 +1,37 @@ +package propertyfx + +import ( + "errors" + + "github.com/ava-labs/gecko/vms/components/verify" + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +var ( + errNilMintOperation = errors.New("nil mint operation") +) + +// MintOperation ... +type MintOperation struct { + MintInput secp256k1fx.Input `serialize:"true"` + MintOutput MintOutput `serialize:"true"` + OwnedOutput OwnedOutput `serialize:"true"` +} + +// Outs ... +func (op *MintOperation) Outs() []verify.Verifiable { + return []verify.Verifiable{ + &op.MintOutput, + &op.OwnedOutput, + } +} + +// Verify ... +func (op *MintOperation) Verify() error { + switch { + case op == nil: + return errNilMintOperation + default: + return verify.All(&op.MintInput, &op.MintOutput, &op.OwnedOutput) + } +} diff --git a/vms/propertyfx/mint_operation_test.go b/vms/propertyfx/mint_operation_test.go new file mode 100644 index 0000000..dc2f350 --- /dev/null +++ b/vms/propertyfx/mint_operation_test.go @@ -0,0 +1,34 @@ +package propertyfx + +import ( + "testing" + + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +func TestMintOperationVerifyNil(t *testing.T) { + op := (*MintOperation)(nil) + if err := op.Verify(); err == nil { + t.Fatalf("nil operation should have failed verification") + } +} + +func TestMintOperationVerifyInvalidOutput(t *testing.T) { + op := MintOperation{ + OwnedOutput: OwnedOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + }, + }, + } + if err := op.Verify(); err == nil { + t.Fatalf("operation should have failed verification") + } +} + +func TestMintOperationOuts(t *testing.T) { + op := MintOperation{} + if outs := op.Outs(); len(outs) != 2 { + t.Fatalf("Wrong number of outputs returned") + } +} diff --git a/vms/propertyfx/mint_output.go b/vms/propertyfx/mint_output.go new file mode 100644 index 0000000..46042da --- /dev/null +++ b/vms/propertyfx/mint_output.go @@ -0,0 +1,10 @@ +package propertyfx + +import ( + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +// MintOutput ... +type MintOutput struct { + secp256k1fx.OutputOwners `serialize:"true"` +} diff --git a/vms/propertyfx/owned_output.go b/vms/propertyfx/owned_output.go new file mode 100644 index 0000000..2ddb81c --- /dev/null +++ b/vms/propertyfx/owned_output.go @@ -0,0 +1,10 @@ +package propertyfx + +import ( + "github.com/ava-labs/gecko/vms/secp256k1fx" +) + +// OwnedOutput ... +type OwnedOutput struct { + secp256k1fx.OutputOwners `serialize:"true"` +}