OSDN Git Service

magnetic program (#402)
authoroysheng <33340252+oysheng@users.noreply.github.com>
Sat, 12 Oct 2019 09:07:11 +0000 (17:07 +0800)
committerPaladz <yzhu101@uottawa.ca>
Sat, 12 Oct 2019 09:07:11 +0000 (17:07 +0800)
* add dex program

* optimise

* adjust code position

* add DecodeP2DCProgram

* optimise code format

* add contract code annotation

* adjust contract

* optimise

* modify contract name

* optimse

* adjust contract

* optimise

* delete redundant code

* optimse

* optimise

* add contract tx test

* add wrong test and ring contract

* optimise

* optimise

* optimise test

* optimise

* optimise

consensus/segwit/segwit.go
protocol/validation/tx_test.go
protocol/validation/vmcontext.go
protocol/vm/vmutil/script.go

index 5f62f33..b10580f 100644 (file)
@@ -4,14 +4,17 @@ import (
        "errors"
 
        "github.com/vapor/consensus"
+       "github.com/vapor/protocol/bc"
        "github.com/vapor/protocol/vm"
        "github.com/vapor/protocol/vm/vmutil"
 )
 
+// IsP2WScript is used to determine whether it is a P2WScript or not
 func IsP2WScript(prog []byte) bool {
        return IsP2WPKHScript(prog) || IsP2WSHScript(prog) || IsStraightforward(prog)
 }
 
+// IsStraightforward is used to determine whether it is a Straightforward script or not
 func IsStraightforward(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -23,6 +26,7 @@ func IsStraightforward(prog []byte) bool {
        return insts[0].Op == vm.OP_TRUE || insts[0].Op == vm.OP_FAIL
 }
 
+// IsP2WPKHScript is used to determine whether it is a P2WPKH script or not
 func IsP2WPKHScript(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -37,6 +41,7 @@ func IsP2WPKHScript(prog []byte) bool {
        return insts[1].Op == vm.OP_DATA_20 && len(insts[1].Data) == consensus.PayToWitnessPubKeyHashDataSize
 }
 
+// IsP2WSHScript is used to determine whether it is a P2WSH script or not
 func IsP2WSHScript(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -51,6 +56,40 @@ func IsP2WSHScript(prog []byte) bool {
        return insts[1].Op == vm.OP_DATA_32 && len(insts[1].Data) == consensus.PayToWitnessScriptHashDataSize
 }
 
+// IsP2WMCScript is used to determine whether it is a P2WMC script or not
+func IsP2WMCScript(prog []byte) bool {
+       insts, err := vm.ParseProgram(prog)
+       if err != nil {
+               return false
+       }
+
+       if len(insts) != 6 {
+               return false
+       }
+
+       if insts[0].Op > vm.OP_16 {
+               return false
+       }
+
+       if insts[1].Op != vm.OP_DATA_32 || len(insts[1].Data) != 32 {
+               return false
+       }
+
+       if !(insts[2].IsPushdata() && insts[3].IsPushdata() && insts[4].IsPushdata()) {
+               return false
+       }
+
+       if _, err = vm.AsInt64(insts[2].Data); err != nil {
+               return false
+       }
+
+       if _, err = vm.AsInt64(insts[3].Data); err != nil {
+               return false
+       }
+       return insts[5].Op == vm.OP_DATA_32 && len(insts[5].Data) == 32
+}
+
+// ConvertP2PKHSigProgram convert standard P2WPKH program into P2PKH program
 func ConvertP2PKHSigProgram(prog []byte) ([]byte, error) {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -62,6 +101,7 @@ func ConvertP2PKHSigProgram(prog []byte) ([]byte, error) {
        return nil, errors.New("unknow P2PKH version number")
 }
 
+// ConvertP2SHProgram convert standard P2WSH program into P2SH program
 func ConvertP2SHProgram(prog []byte) ([]byte, error) {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
@@ -73,6 +113,45 @@ func ConvertP2SHProgram(prog []byte) ([]byte, error) {
        return nil, errors.New("unknow P2SHP version number")
 }
 
+// ConvertP2MCProgram convert standard P2WMC program into P2MC program
+func ConvertP2MCProgram(prog []byte) ([]byte, error) {
+       magneticContractArgs, err := DecodeP2WMCProgram(prog)
+       if err != nil {
+               return nil, err
+       }
+       return vmutil.P2MCProgram(*magneticContractArgs)
+}
+
+// DecodeP2WMCProgram parse standard P2WMC arguments to magneticContractArgs
+func DecodeP2WMCProgram(prog []byte) (*vmutil.MagneticContractArgs, error) {
+       if !IsP2WMCScript(prog) {
+               return nil, errors.New("invalid P2MC program")
+       }
+
+       insts, err := vm.ParseProgram(prog)
+       if err != nil {
+               return nil, err
+       }
+
+       magneticContractArgs := &vmutil.MagneticContractArgs{
+               SellerProgram: insts[4].Data,
+               SellerKey:     insts[5].Data,
+       }
+       requestedAsset := [32]byte{}
+       copy(requestedAsset[:], insts[1].Data)
+       magneticContractArgs.RequestedAsset = bc.NewAssetID(requestedAsset)
+
+       if magneticContractArgs.RatioMolecule, err = vm.AsInt64(insts[2].Data); err != nil {
+               return nil, err
+       }
+
+       if magneticContractArgs.RatioDenominator, err = vm.AsInt64(insts[3].Data); err != nil {
+               return nil, err
+       }
+       return magneticContractArgs, nil
+}
+
+// GetHashFromStandardProg get hash from standard program
 func GetHashFromStandardProg(prog []byte) ([]byte, error) {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
index 484cfa2..7db51ae 100644 (file)
@@ -877,6 +877,285 @@ func TestValidateTxVersion(t *testing.T) {
        }
 }
 
+func TestMagneticContractTx(t *testing.T) {
+       buyerArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 1},
+               RatioMolecule:    1,
+               RatioDenominator: 2,
+               SellerProgram:    []byte{0x51},
+               SellerKey:        testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
+       }
+
+       sellerArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 2},
+               RatioMolecule:    2,
+               RatioDenominator: 1,
+               SellerProgram:    []byte{0x52},
+               SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+       }
+
+       programBuyer, err := vmutil.P2WMCProgram(buyerArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       programSeller, err := vmutil.P2WMCProgram(sellerArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       cases := []struct {
+               desc  string
+               block *bc.Block
+               err   error
+       }{
+               {
+                       desc: "contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "first contract partial trade, second contract full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, programSeller),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "first contract full trade, second contract partial trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(1), vm.Int64Bytes(0)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 300000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, programBuyer),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "cancel magnetic contract",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{testutil.MustDecodeHexString("0a72a2b2944ec9b4bcdef392e6c532effc77ea536809fa290a12e39df7651851a9939e23e492369dc8936e0ebf3ecd1de4e9077d0593bd3fcb5874fb26dfc60a"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "wrong signature with cancel magnetic contract",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{testutil.MustDecodeHexString("686b983a8de1893ef723144389fd1f07b12b048f52f389faa863243195931d5732dbfd15470b43ed63d5067900718cf94f137073f4a972d277bbd967b022545d"), vm.Int64Bytes(0), vm.Int64Bytes(2)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 0, programSeller),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+               {
+                       desc: "wrong output amount with contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, []byte{0x55}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+               {
+                       desc: "wrong output assetID with contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 100000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 200000000, 0, programBuyer),
+                                                       types.NewSpendInput(nil, bc.Hash{V0: 30}, bc.AssetID{V0: 1}, 200000000, 0, []byte{0x51}),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(bc.AssetID{V0: 1}, 200000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 100000000, buyerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 200000000, []byte{0x55}),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+               {
+                       desc: "wrong output change program with first contract partial trade and second contract full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(100000000), vm.Int64Bytes(0), vm.Int64Bytes(0)}, bc.Hash{V0: 10}, buyerArgs.RequestedAsset, 200000000, 1, programSeller),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, sellerArgs.RequestedAsset, 100000000, 0, programBuyer),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(sellerArgs.RequestedAsset, 100000000, sellerArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 150000000, []byte{0x55}),
+                                                       types.NewIntraChainOutput(buyerArgs.RequestedAsset, 50000000, buyerArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: vm.ErrFalseVMResult,
+               },
+       }
+
+       for i, c := range cases {
+               if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
+                       t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
+               }
+       }
+}
+
+func TestRingMagneticContractTx(t *testing.T) {
+       aliceArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 1},
+               RatioMolecule:    2,
+               RatioDenominator: 1,
+               SellerProgram:    []byte{0x51},
+               SellerKey:        testutil.MustDecodeHexString("960ecabafb88ba460a40912841afecebf0e84884178611ac97210e327c0d1173"),
+       }
+
+       bobArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 2},
+               RatioMolecule:    2,
+               RatioDenominator: 1,
+               SellerProgram:    []byte{0x52},
+               SellerKey:        testutil.MustDecodeHexString("ad79ec6bd3a6d6dbe4d0ee902afc99a12b9702fb63edce5f651db3081d868b75"),
+       }
+
+       jackArgs := vmutil.MagneticContractArgs{
+               RequestedAsset:   bc.AssetID{V0: 3},
+               RatioMolecule:    1,
+               RatioDenominator: 4,
+               SellerProgram:    []byte{0x53},
+               SellerKey:        testutil.MustDecodeHexString("9c19a91988c62046c2767bd7e9999b0c142891b9ebf467bfa59210b435cb0de7"),
+       }
+
+       aliceProgram, err := vmutil.P2WMCProgram(aliceArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       bobProgram, err := vmutil.P2WMCProgram(bobArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       jackProgram, err := vmutil.P2WMCProgram(jackArgs)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       cases := []struct {
+               desc  string
+               block *bc.Block
+               err   error
+       }{
+               {
+                       desc: "contracts all full trade",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{Version: 0},
+                               Transactions: []*bc.Tx{
+                                       types.MapTx(&types.TxData{
+                                               SerializedSize: 1,
+                                               Inputs: []*types.TxInput{
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(0), vm.Int64Bytes(1)}, bc.Hash{V0: 10}, jackArgs.RequestedAsset, 100000000, 0, aliceProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(1), vm.Int64Bytes(1)}, bc.Hash{V0: 20}, aliceArgs.RequestedAsset, 200000000, 0, bobProgram),
+                                                       types.NewSpendInput([][]byte{vm.Int64Bytes(2), vm.Int64Bytes(1)}, bc.Hash{V0: 30}, bobArgs.RequestedAsset, 400000000, 0, jackProgram),
+                                               },
+                                               Outputs: []*types.TxOutput{
+                                                       types.NewIntraChainOutput(aliceArgs.RequestedAsset, 200000000, aliceArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(bobArgs.RequestedAsset, 400000000, bobArgs.SellerProgram),
+                                                       types.NewIntraChainOutput(jackArgs.RequestedAsset, 100000000, jackArgs.SellerProgram),
+                                               },
+                                       }),
+                               },
+                       },
+                       err: nil,
+               },
+       }
+
+       for i, c := range cases {
+               if _, err := ValidateTx(c.block.Transactions[0], c.block); rootErr(err) != c.err {
+                       t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, rootErr(err), c.err)
+               }
+       }
+}
+
 // A txFixture is returned by sample (below) to produce a sample
 // transaction, which takes a separate, optional _input_ txFixture to
 // affect the transaction that's built. The components of the
index 7ac617c..98eae57 100644 (file)
@@ -98,12 +98,17 @@ func NewTxVMContext(vs *validationState, entry bc.Entry, prog *bc.Program, args
 }
 
 func witnessProgram(prog []byte) []byte {
-       if segwit.IsP2WPKHScript(prog) {
-               if witnessProg, err := segwit.ConvertP2PKHSigProgram([]byte(prog)); err == nil {
+       switch {
+       case segwit.IsP2WPKHScript(prog):
+               if witnessProg, err := segwit.ConvertP2PKHSigProgram(prog); err == nil {
                        return witnessProg
                }
-       } else if segwit.IsP2WSHScript(prog) {
-               if witnessProg, err := segwit.ConvertP2SHProgram([]byte(prog)); err == nil {
+       case segwit.IsP2WSHScript(prog):
+               if witnessProg, err := segwit.ConvertP2SHProgram(prog); err == nil {
+                       return witnessProg
+               }
+       case segwit.IsP2WMCScript(prog):
+               if witnessProg, err := segwit.ConvertP2MCProgram(prog); err == nil {
                        return witnessProg
                }
        }
index adc0d77..201eeaf 100644 (file)
@@ -3,6 +3,7 @@ package vmutil
 import (
        "github.com/vapor/crypto/ed25519"
        "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc"
        "github.com/vapor/protocol/vm"
 )
 
@@ -12,6 +13,15 @@ var (
        ErrMultisigFormat = errors.New("bad multisig program format")
 )
 
+// MagneticContractArgs is a struct for magnetic contract arguments
+type MagneticContractArgs struct {
+       RequestedAsset   bc.AssetID
+       RatioMolecule    int64
+       RatioDenominator int64
+       SellerProgram    []byte
+       SellerKey        []byte
+}
+
 // IsUnspendable checks if a contorl program is absolute failed
 func IsUnspendable(prog []byte) bool {
        return len(prog) > 0 && prog[0] == byte(vm.OP_FAIL)
@@ -133,3 +143,227 @@ func checkMultiSigParams(nrequired, npubkeys int64) error {
        }
        return nil
 }
+
+// P2WMCProgram return the segwit pay to magnetic contract
+func P2WMCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
+       builder := NewBuilder()
+       builder.AddInt64(0)
+       builder.AddData(magneticContractArgs.RequestedAsset.Bytes())
+       builder.AddInt64(magneticContractArgs.RatioMolecule)
+       builder.AddInt64(magneticContractArgs.RatioDenominator)
+       builder.AddData(magneticContractArgs.SellerProgram)
+       builder.AddData(magneticContractArgs.SellerKey)
+       return builder.Build()
+}
+
+// P2MCProgram generates the script for control with magnetic contract
+//
+// MagneticContract source code:
+// contract MagneticContract(requestedAsset: Asset,
+//                           ratioMolecule: Integer,
+//                           ratioDenominator: Integer,
+//                           sellerProgram: Program,
+//                           standardProgram: Program,
+//                           sellerKey: PublicKey) locks valueAmount of valueAsset {
+//  clause partialTrade(exchangeAmount: Amount) {
+//      define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioMolecule
+//      verify actualAmount > 0 && actualAmount < valueAmount
+//   lock exchangeAmount of requestedAsset with sellerProgram
+//   lock valueAmount-actualAmount of valueAsset with standardProgram
+//   unlock actualAmount of valueAsset
+//  }
+//  clause fullTrade() {
+//   define requestedAmount: Integer = valueAmount * ratioMolecule / ratioDenominator
+//   verify requestedAmount > 0
+//   lock requestedAmount of requestedAsset with sellerProgram
+//   unlock valueAmount of valueAsset
+//  }
+//  clause cancel(sellerSig: Signature) {
+//   verify checkTxSig(sellerKey, sellerSig)
+//   unlock valueAmount of valueAsset
+//  }
+// }
+//
+// contract stack flow:
+// 7                        [... <position> <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset 7]
+// ROLL                     [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <position>]
+// TOALTSTACK               [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// 6                        [... <clause selector> sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset 6]
+// ROLL                     [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <clause selector>]
+// DUP                      [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <clause selector> <clause selector>]
+// 2                        [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <clause selector> <clause selector> 2]
+// NUMEQUAL                 [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <clause selector> (<clause selector> == 2)]
+// JUMPIF:$cancel           [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <clause selector>]
+// JUMPIF:$fullTrade        [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// $partialTrade            [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// 6                        [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset 6]
+// PICK                     [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset exchangeAmount]
+// 3                        [... exchangeAmount sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset exchangeAmount 3]
+// ROLL                     [... exchangeAmount sellerKey standardProgram sellerProgram ratioMolecule requestedAsset exchangeAmount ratioDenominator]
+// MUL                      [... exchangeAmount sellerKey standardProgram sellerProgram ratioMolecule requestedAsset (exchangeAmount * ratioDenominator)]
+// 2                        [... exchangeAmount sellerKey standardProgram sellerProgram ratioMolecule requestedAsset (exchangeAmount * ratioDenominator) 2]
+// ROLL                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset (exchangeAmount * ratioDenominator) ratioMolecule]
+// DIV                      [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount]
+// AMOUNT                   [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount]
+// OVER                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount]
+// 0                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount actualAmount 0]
+// GREATERTHAN              [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0)]
+// 2                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) 2]
+// PICK                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount]
+// 2                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount valueAmount (actualAmount > 0) actualAmount 2]
+// ROLL                     [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount (actualAmount > 0) actualAmount valueAmount]
+// LESSTHAN                 [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount (actualAmount > 0) (actualAmount < valueAmount)]
+// BOOLAND                  [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount ((actualAmount > 0) && (actualAmount < valueAmount))]
+// VERIFY                   [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount]
+// FROMALTSTACK             [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
+// DUP                      [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> <position>]
+// TOALTSTACK               [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position>]
+// 6                        [... exchangeAmount sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> 6]
+// ROLL                     [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount]
+// 3                        [... sellerKey standardProgram sellerProgram requestedAsset actualAmount <position> exchangeAmount 3]
+// ROLL                     [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset]
+// 1                        [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset 1]
+// 5                        [... sellerKey standardProgram sellerProgram actualAmount <position> exchangeAmount requestedAsset 1 5]
+// ROLL                     [... sellerKey standardProgram actualAmount <position> exchangeAmount requestedAsset 1 sellerProgram]
+// CHECKOUTPUT              [... sellerKey standardProgram actualAmount checkOutput(exchangeAmount, requestedAsset, sellerProgram)]
+// VERIFY                   [... sellerKey standardProgram actualAmount]
+// FROMALTSTACK             [... sellerKey standardProgram actualAmount <position>]
+// 1                        [... sellerKey standardProgram actualAmount <position> 1]
+// ADD                      [... sellerKey standardProgram actualAmount (<position> + 1)]
+// AMOUNT                   [... sellerKey standardProgram actualAmount (<position> + 1) valueAmount]
+// 2                        [... sellerKey standardProgram actualAmount (<position> + 1) valueAmount 2]
+// ROLL                     [... sellerKey standardProgram (<position> + 1) valueAmount actualAmount]
+// SUB                      [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount)]
+// ASSET                    [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset]
+// 1                        [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset 1]
+// 4                        [... sellerKey standardProgram (<position> + 1) (valueAmount - actualAmount) valueAsset 1 4]
+// ROLL                     [... sellerKey (<position> + 1) (valueAmount - actualAmount) valueAsset 1 standardProgram]
+// CHECKOUTPUT              [... sellerKey checkOutput((valueAmount - actualAmount), valueAsset, standardProgram)]
+// JUMP:$_end               [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// $fullTrade               [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// AMOUNT                   [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset valueAmount]
+// 2                        [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset valueAmount 2]
+// ROLL                     [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset valueAmount ratioMolecule]
+// MUL                      [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset (valueAmount * ratioMolecule)]
+// 2                        [... sellerKey standardProgram sellerProgram ratioDenominator requestedAsset (valueAmount * ratioMolecule) 2]
+// ROLL                     [... sellerKey standardProgram sellerProgram requestedAsset (valueAmount * ratioMolecule) ratioDenominator]
+// DIV                      [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// DUP                      [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount]
+// 0                        [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount requestedAmount 0]
+// GREATERTHAN              [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount (requestedAmount > 0)]
+// VERIFY                   [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount]
+// FROMALTSTACK             [... sellerKey standardProgram sellerProgram requestedAsset requestedAmount <position>]
+// SWAP                     [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount]
+// 2                        [... sellerKey standardProgram sellerProgram requestedAsset <position> requestedAmount 2]
+// ROLL                     [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset]
+// 1                        [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset 1]
+// 4                        [... sellerKey standardProgram sellerProgram <position> requestedAmount requestedAsset 1 4]
+// ROLL                     [... sellerKey standardProgram <position> requestedAmount requestedAsset 1 sellerProgram]
+// CHECKOUTPUT              [... sellerKey standardProgram checkOutput(requestedAmount, requestedAsset, sellerProgram)]
+// JUMP:$_end               [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// $cancel                  [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset <clause selector>]
+// DROP                     [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+// 6                        [... sellerSig sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset 6]
+// ROLL                     [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset sellerSig]
+// 6                        [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset sellerSig 6]
+// ROLL                     [... standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset sellerSig sellerKey]
+// TXSIGHASH SWAP CHECKSIG  [... standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset checkTxSig(sellerKey, sellerSig)]
+// $_end                    [... sellerKey standardProgram sellerProgram ratioDenominator ratioMolecule requestedAsset]
+func P2MCProgram(magneticContractArgs MagneticContractArgs) ([]byte, error) {
+       standardProgram, err := P2WMCProgram(magneticContractArgs)
+       if err != nil {
+               return nil, err
+       }
+
+       builder := NewBuilder()
+       // contract arguments
+       builder.AddData(magneticContractArgs.SellerKey)
+       builder.AddData(standardProgram)
+       builder.AddData(magneticContractArgs.SellerProgram)
+       builder.AddInt64(magneticContractArgs.RatioDenominator)
+       builder.AddInt64(magneticContractArgs.RatioMolecule)
+       builder.AddData(magneticContractArgs.RequestedAsset.Bytes())
+
+       // contract instructions
+       builder.AddOp(vm.OP_7)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_TOALTSTACK)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_2)
+       builder.AddOp(vm.OP_NUMEQUAL)
+       builder.AddJumpIf(0)
+       builder.AddJumpIf(1)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_PICK)
+       builder.AddOp(vm.OP_3)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_MUL)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_DIV)
+       builder.AddOp(vm.OP_AMOUNT)
+       builder.AddOp(vm.OP_OVER)
+       builder.AddOp(vm.OP_0)
+       builder.AddOp(vm.OP_GREATERTHAN)
+       builder.AddOp(vm.OP_2)
+       builder.AddOp(vm.OP_PICK)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_LESSTHAN)
+       builder.AddOp(vm.OP_BOOLAND)
+       builder.AddOp(vm.OP_VERIFY)
+       builder.AddOp(vm.OP_FROMALTSTACK)
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_TOALTSTACK)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_3)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_5)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_CHECKOUTPUT)
+       builder.AddOp(vm.OP_VERIFY)
+       builder.AddOp(vm.OP_FROMALTSTACK)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_ADD)
+       builder.AddOp(vm.OP_AMOUNT)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_SUB)
+       builder.AddOp(vm.OP_ASSET)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_4)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_CHECKOUTPUT)
+       builder.AddJump(2)
+       builder.SetJumpTarget(1)
+       builder.AddOp(vm.OP_AMOUNT)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_MUL)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_DIV)
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_0)
+       builder.AddOp(vm.OP_GREATERTHAN)
+       builder.AddOp(vm.OP_VERIFY)
+       builder.AddOp(vm.OP_FROMALTSTACK)
+       builder.AddOp(vm.OP_SWAP)
+       builder.AddOp(vm.OP_ROT)
+       builder.AddOp(vm.OP_1)
+       builder.AddOp(vm.OP_4)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_CHECKOUTPUT)
+       builder.AddJump(3)
+       builder.SetJumpTarget(0)
+       builder.AddOp(vm.OP_DROP)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_6)
+       builder.AddOp(vm.OP_ROLL)
+       builder.AddOp(vm.OP_TXSIGHASH)
+       builder.AddOp(vm.OP_SWAP)
+       builder.AddOp(vm.OP_CHECKSIG)
+       builder.SetJumpTarget(2)
+       builder.SetJumpTarget(3)
+       return builder.Build()
+}