#!/usr/bin/python3
"""
Copyright 2022 Wormhole Project Contributors

Licensed under the Apache License, Version 2.0 (the "License");

you may not use this file except in compliance with the License.

You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

"""
from typing import List, Tuple, Dict, Any, Optional, Union

from pyteal.ast import *
from pyteal.types import *
from pyteal.compiler import *
from pyteal.ir import *
from globals import *
from inlineasm import *

from algosdk.v2client.algod import AlgodClient

from TmplSig import TmplSig
from local_blob import LocalBlob

import pprint
import sys

max_keys = 15
max_bytes_per_key = 127
bits_per_byte = 8

bits_per_key = max_bytes_per_key * bits_per_byte
max_bytes = max_bytes_per_key * max_keys
max_bits = bits_per_byte * max_bytes

portal_transfer_selector = MethodSignature("portal_transfer(byte[])byte[]")

def fullyCompileContract(genTeal, client: AlgodClient, contract: Expr, name, devmode) -> bytes:
    if devmode:
        teal = compileTeal(contract, mode=Mode.Application, version=6, assembleConstants=True)
    else:
        teal = compileTeal(contract, mode=Mode.Application, version=6, assembleConstants=True, optimize=OptimizeOptions(scratch_slots=True))

    if genTeal:
        with open(name, "w") as f:
            print("Writing " + name)
            f.write(teal)
    else:
        with open(name, "r") as f:
            print("Reading " + name)
            teal = f.read()

    response = client.compile(teal)
    return response

def clear_token_bridge():
    return Int(1)

def approve_token_bridge(seed_amt: int, tmpl_sig: TmplSig, devMode: bool):
    blob = LocalBlob()
    tidx = ScratchVar()
    mfee = ScratchVar()

    def MagicAssert(a) -> Expr:
        if devMode:
            from inspect import currentframe
            return Assert(And(a, Int(currentframe().f_back.f_lineno)))
        else:
            return Assert(a)

    @Subroutine(TealType.uint64)
    def governanceSet() -> Expr:
        maybe = App.globalGetEx(App.globalGet(Bytes("coreid")), Bytes("currentGuardianSetIndex"))
        return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())

    @Subroutine(TealType.uint64)
    def getMessageFee() -> Expr:
        maybe = App.globalGetEx(App.globalGet(Bytes("coreid")), Bytes("MessageFee"))
        return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())

    @Subroutine(TealType.bytes)
    def getAppAddress(appid : Expr) -> Expr:
        maybe = AppParam.address(appid)
        return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())

    def assert_common_checks(e) -> Expr:
        return MagicAssert(And(
            e.rekey_to() == Global.zero_address(),
            e.close_remainder_to() == Global.zero_address(),
            e.asset_close_to() == Global.zero_address()
        ))

    @Subroutine(TealType.none)
    def checkFeePmt(off : Expr):
        return Seq([
            If(mfee.load() > Int(0), Seq([
                    tidx.store(Txn.group_index() - off),
                    MagicAssert(And(
                        Gtxn[tidx.load()].type_enum() == TxnType.Payment,
                        Gtxn[tidx.load()].sender() == Txn.sender(),
                        Gtxn[tidx.load()].receiver() == Global.current_application_address(),
                        Gtxn[tidx.load()].amount() >= mfee.load()
                    )),
                    assert_common_checks(Gtxn[tidx.load()])
            ]))
        ])

    @Subroutine(TealType.none)
    def sendMfee():
        return Seq([
            If (mfee.load() > Int(0), Seq([
                    InnerTxnBuilder.SetFields(
                        {
                            TxnField.type_enum: TxnType.Payment,
                            TxnField.receiver: App.globalGet(Bytes("coreAddr")),
                            TxnField.amount: mfee.load(),
                            TxnField.fee: Int(0),
                        }
                    ),
                    InnerTxnBuilder.Next(),
            ])),
        ])
    
    @Subroutine(TealType.bytes)
    def encode_uvarint(val: Expr, b: Expr):
        buff = ScratchVar()
        return Seq(
            buff.store(b),
            Concat(
                buff.load(),
                If(
                        val >= Int(128),
                        encode_uvarint(
                            val >> Int(7),
                            Extract(Itob((val & Int(255)) | Int(128)), Int(7), Int(1)),
                        ),
                        Extract(Itob(val & Int(255)), Int(7), Int(1)),
                ),
            ),
        )

    @Subroutine(TealType.bytes)
    def trim_bytes(str: Expr):
        len = ScratchVar()
        off = ScratchVar()
        zero = ScratchVar()
        r = ScratchVar()

        return Seq([
            r.store(str),

            len.store(Len(r.load())),
            zero.store(BytesZero(Int(1))),
            off.store(Int(0)),

            While(off.load() < len.load()).Do(Seq([
                If(Extract(r.load(), off.load(), Int(1)) == zero.load()).Then(Seq([
                        r.store(Extract(r.load(), Int(0), off.load())),
                        off.store(len.load())
                ])),
                    off.store(off.load() + Int(1))
            ])),
            r.load()
        ])

    @Subroutine(TealType.uint64)
    def getFactor(dec: Expr):
        return Cond(
            [dec == Int(9),  Int(10)],
            [dec == Int(10), Int(100)],
            [dec == Int(11), Int(1000)],
            [dec == Int(12), Int(10000)],
            [dec == Int(13), Int(100000)],
            [dec == Int(14), Int(1000000)],
            [dec == Int(15), Int(10000000)],
            [dec == Int(16), Int(100000000)],
            [dec >  Int(16), Seq(Reject(), Int(1))],
            [dec < Int(9), Int(1)]
        )

    @Subroutine(TealType.bytes)
    def get_sig_address(acct_seq_start: Expr, emitter: Expr):
        # We could iterate over N items and encode them for a more general interface
        # but we inline them directly here

        return Sha512_256(
            Concat(
                Bytes("Program"),
                # ADDR_IDX aka sequence start
                tmpl_sig.get_bytecode_chunk(0),
                encode_uvarint(acct_seq_start, Bytes("")),

                # EMMITTER_ID
                tmpl_sig.get_bytecode_chunk(1),
                encode_uvarint(Len(emitter), Bytes("")),
                emitter,

                # APP_ID
                tmpl_sig.get_bytecode_chunk(2),
                encode_uvarint(Global.current_application_id(), Bytes("")),

                # TMPL_APP_ADDRESS
                tmpl_sig.get_bytecode_chunk(3),
                encode_uvarint(Len(Global.current_application_address()), Bytes("")),
                Global.current_application_address(),

                tmpl_sig.get_bytecode_chunk(4),
            )
        )

    def governance():
        off = ScratchVar()
        a = ScratchVar()
        targetChain = ScratchVar()
        chain = ScratchVar()
        emitter = ScratchVar()
        set = ScratchVar()
        idx = ScratchVar()
        verifyIdx = ScratchVar()

        verifyVAA = Gtxn[verifyIdx.load()]

    
        return Seq([
            checkForDuplicate(),

            # All governance must be done with the most recent guardian set...
            set.store(governanceSet()),
            idx.store(Extract(Txn.application_args[1], Int(1), Int(4))),
            MagicAssert(Btoi(idx.load()) == set.load()),

            # The offset of the chain
            off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)), 

            verifyIdx.store(Txn.group_index() - Int(1)),

            MagicAssert(And(
                # Did verifyVAA pass?
                verifyVAA.type_enum() == TxnType.ApplicationCall,
                verifyVAA.application_id() == App.globalGet(Bytes("coreid")),
                verifyVAA.application_args[0] == Bytes("verifyVAA"),
                verifyVAA.sender() == Txn.sender(),

                # Lets see if the vaa we are about to process was actually verified by the core
                verifyVAA.application_args[1] == Txn.application_args[1],

                # We all opted into the same accounts?
                verifyVAA.accounts[0] == Txn.accounts[0],
                verifyVAA.accounts[1] == Txn.accounts[1],
                verifyVAA.accounts[2] == Txn.accounts[2],

                # Better be the right emitters
                Extract(Txn.application_args[1], off.load(), Int(2)) == Bytes("base16", "0001"),
                Extract(Txn.application_args[1], off.load() + Int(2), Int(32)) == Concat(BytesZero(Int(31)), Bytes("base16", "04")),
            )),

            assert_common_checks(verifyVAA),
            assert_common_checks(Txn),

            # correct module?
            MagicAssert(Extract(Txn.application_args[1], off.load() + Int(43), Int(32)) == Concat(BytesZero(Int(21)), Bytes("base16", "546f6b656e427269646765"))),
            a.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(75), Int(1)))),
            off.store(off.load() + Int(76)),

            Cond( 
                [a.load() == Int(1), Seq([
                    targetChain.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(2)))),

                    MagicAssert(Or((targetChain.load() == Int(0)), (targetChain.load() == Int(8)))),

                    chain.store(Extract(Txn.application_args[1], off.load() + Int(2), Int(2))),
                    emitter.store(Extract(Txn.application_args[1], off.load() + Int(4), Int(32))),

                    # Can I only register once?  Rumor says yes
                    MagicAssert(App.globalGet(Concat(Bytes("Chain"), chain.load())) == Int(0)),

                    App.globalPut(Concat(Bytes("Chain"), chain.load()), emitter.load()),
                ])],
                [a.load() == Int(2), Seq([
                    MagicAssert(Extract(Txn.application_args[1], off.load(), Int(2)) == Bytes("base16", "0008")),
                    App.globalPut(Bytes("validUpdateApproveHash"), Extract(Txn.application_args[1], off.load() + Int(2), Int(32)))
                ])]
            ),

            Approve()
        ])
    
    def receiveAttest():
        me = Global.current_application_address()
        off = ScratchVar()
        
        Address = ScratchVar()
        Chain = ScratchVar()
        FromChain = ScratchVar()
        Decimals = ScratchVar()
        Symbol = ScratchVar()
        Name = ScratchVar()

        asset = ScratchVar()
        buf = ScratchVar()
        c = ScratchVar()
        a = ScratchVar()

        return Seq([
            checkForDuplicate(),

            tidx.store(Txn.group_index() - Int(4)),
            MagicAssert(And(
                # Lets see if the vaa we are about to process was actually verified by the core
                Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
                Gtxn[tidx.load()].application_id() == App.globalGet(Bytes("coreid")),
                Gtxn[tidx.load()].application_args[0] == Bytes("verifyVAA"),
                Gtxn[tidx.load()].sender() == Txn.sender(),

                # we are all taking about the same vaa?
                Gtxn[tidx.load()].application_args[1] == Txn.application_args[1],

                # We all opted into the same accounts?
                Gtxn[tidx.load()].accounts[0] == Txn.accounts[0],
                Gtxn[tidx.load()].accounts[1] == Txn.accounts[1],
                Gtxn[tidx.load()].accounts[2] == Txn.accounts[2],
                )),
            assert_common_checks(Gtxn[tidx.load()]),
                
            tidx.store(Txn.group_index() - Int(3)),
            MagicAssert(And(
                # Did the user pay the lsig to attest a new product?
                Gtxn[tidx.load()].type_enum() == TxnType.Payment,
                Gtxn[tidx.load()].amount() >= Int(100000),
                Gtxn[tidx.load()].sender() == Txn.sender(),
                Gtxn[tidx.load()].receiver() == Txn.accounts[3],
                )),
            assert_common_checks(Gtxn[tidx.load()]),

            tidx.store(Txn.group_index() - Int(2)),
            MagicAssert(And(
                # We had to buy some extra CPU
                Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
                Gtxn[tidx.load()].application_id() == Global.current_application_id(),
                Gtxn[tidx.load()].application_args[0] == Bytes("nop"),
                Gtxn[tidx.load()].sender() == Txn.sender(),
                )),
            assert_common_checks(Gtxn[tidx.load()]),

            tidx.store(Txn.group_index() - Int(1)),
            MagicAssert(And(
                Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
                Gtxn[tidx.load()].application_id() == Global.current_application_id(),
                Gtxn[tidx.load()].application_args[0] == Bytes("nop"),
                Gtxn[tidx.load()].sender() == Txn.sender(),
                
                (Global.group_size() - Int(1)) == Txn.group_index()    # This should be the last entry...
            )),
            assert_common_checks(Gtxn[tidx.load()]),

            off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(6) + Int(8)), # The offset of the chain
            Chain.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(2)))),

            # Make sure that the emitter on the sending chain is correct for the token bridge
            MagicAssert(App.globalGet(Concat(Bytes("Chain"), Extract(Txn.application_args[1], off.load(), Int(2)))) 
                   == Extract(Txn.application_args[1], off.load() + Int(2), Int(32))),
            
            off.store(off.load()+Int(43)),

            MagicAssert(Int(2) ==      Btoi(Extract(Txn.application_args[1], off.load(),      Int(1)))),
            Address.store(             Extract(Txn.application_args[1], off.load() + Int(1),  Int(32))),
            
            FromChain.store(      Btoi(Extract(Txn.application_args[1], off.load() + Int(33), Int(2)))),
            Decimals.store(       Btoi(Extract(Txn.application_args[1], off.load() + Int(35), Int(1)))),
            Symbol.store(              Extract(Txn.application_args[1], off.load() + Int(36), Int(32))),
            Name.store(                Extract(Txn.application_args[1], off.load() + Int(68), Int(32))),

            # Lets trim this... seems these are limited to 7 characters
            Symbol.store(trim_bytes(Symbol.load())),
            If (Len(Symbol.load()) > Int(7), Symbol.store(Extract(Symbol.load(), Int(0), Int(7)))),
            Name.store(Concat(trim_bytes(Name.load()), Bytes(" (Wormhole)"))),

            # Due to constrains on some supported chains, all token
            # amounts passed through the token bridge are truncated to
            # a maximum of 8 decimals. 
            # 
            # Any chains implementation must make sure that of any
            # token only ever MaxUint64 units (post-shifting) are
            # bridged into the wormhole network at any given time (all
            # target chains combined), even tough the slot is 32 bytes
            # long (theoretically fitting uint256). 
            If(Decimals.load() > Int(8), Decimals.store(Int(8))),

            #   This confirms the user gave us access to the correct memory for this asset..
            MagicAssert(Txn.accounts[3] == get_sig_address(FromChain.load(), Address.load())),

            # Lets see if we've seen this asset before
            asset.store(blob.read(Int(3), Int(0), Int(8))),

            # The # offset to the digest
            off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(6)), 

            # New asset
            If(asset.load() == Itob(Int(0))).Then(Seq([
                    InnerTxnBuilder.Begin(),
                    InnerTxnBuilder.SetFields(
                        {
                            TxnField.sender: Txn.accounts[3],
                            TxnField.type_enum: TxnType.AssetConfig,
                            TxnField.config_asset_name: Name.load(),
                            TxnField.config_asset_unit_name: Symbol.load(),
                            TxnField.config_asset_total: Int(18446744073709551614),
                            TxnField.config_asset_decimals: Decimals.load(),
                            TxnField.config_asset_manager: me,
                            TxnField.config_asset_reserve: me,

                        # We cannot freeze or clawback assets... per the spirit of 
                            TxnField.config_asset_freeze: Global.zero_address(),
                            TxnField.config_asset_clawback: Global.zero_address(),

                            TxnField.fee: Int(0),
                        }
                    ),
                    InnerTxnBuilder.Submit(),

                    asset.store(Itob(InnerTxn.created_asset_id())),
                    Pop(blob.write(Int(3), Int(0), asset.load())),
                    blob.meta(Int(3), Bytes("asset"))
            ])),

            # We save away the entire digest that created this asset in case we ever need to reproduce it while sending this
            # coin to another chain

            buf.store(Txn.application_args[1]),
            Pop(blob.write(Int(3), Int(8), Extract(buf.load(), off.load(), Len(buf.load()) - off.load()))),

            Approve()
        ])

    def completeTransfer():
        me = Global.current_application_address()
        off = ScratchVar()
        
        Chain = ScratchVar()
        Emitter = ScratchVar()

        Amount = ScratchVar()
        Origin = ScratchVar()
        OriginChain = ScratchVar()
        Destination = ScratchVar()
        DestChain = ScratchVar()
        Fee = ScratchVar()
        asset = ScratchVar()

        factor = ScratchVar()
        d = ScratchVar()
        zb = ScratchVar()
        action = ScratchVar()
        aid = ScratchVar()
        
        return Seq([
            checkForDuplicate(),

            zb.store(BytesZero(Int(32))),

            tidx.store(Txn.group_index() - Int(1)),

            MagicAssert(And(
                # Lets see if the vaa we are about to process was actually verified by the core
                Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
                Gtxn[tidx.load()].application_id() == App.globalGet(Bytes("coreid")),
                Gtxn[tidx.load()].application_args[0] == Bytes("verifyVAA"),
                Gtxn[tidx.load()].sender() == Txn.sender(),

                # Lets see if the vaa we are about to process was actually verified by the core
                Gtxn[tidx.load()].application_args[1] == Txn.application_args[1],

                # We all opted into the same accounts?
                Gtxn[tidx.load()].accounts[0] == Txn.accounts[0],
                Gtxn[tidx.load()].accounts[1] == Txn.accounts[1],
                Gtxn[tidx.load()].accounts[2] == Txn.accounts[2]
            )),
            assert_common_checks(Gtxn[tidx.load()]),
            assert_common_checks(Txn),

            off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(6) + Int(8)), # The offset of the chain

            Chain.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(2)))),
            Emitter.store(Extract(Txn.application_args[1], off.load() + Int(2), Int(32))),

            # We coming from the correct emitter on the sending chain for the token bridge
            # ... This is 90% of the security...
            If(Chain.load() == Int(8),
               MagicAssert(Global.current_application_address() == Emitter.load()), # This came from us?
               MagicAssert(App.globalGet(Concat(Bytes("Chain"), Extract(Txn.application_args[1], off.load(), Int(2)))) == Emitter.load())),

            off.store(off.load()+Int(43)),

            # This is a transfer message... right?
            action.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(1)))),

            MagicAssert(Or(action.load() == Int(1), action.load() == Int(3))),

            MagicAssert(Extract(Txn.application_args[1], off.load() + Int(1), Int(24)) == Extract(zb.load(), Int(0), Int(24))),
            Amount.store(        Btoi(Extract(Txn.application_args[1], off.load() + Int(25), Int(8)))),  # uint256

            Origin.store(             Extract(Txn.application_args[1], off.load() + Int(33), Int(32))),
            OriginChain.store(   Btoi(Extract(Txn.application_args[1], off.load() + Int(65), Int(2)))),
            Destination.store(        Extract(Txn.application_args[1], off.load() + Int(67), Int(32))),
            DestChain.store(     Btoi(Extract(Txn.application_args[1], off.load() + Int(99), Int(2)))),

            # This directed at us?
            MagicAssert(DestChain.load() == Int(8)),

            If (action.load() == Int(3), Seq([
                    aid.store(Btoi(Extract(Destination.load(), Int(24), Int(8)))), # The destination is the appid in a payload3
                    tidx.store(Txn.group_index() + Int(1)),
                    MagicAssert(And(
                        Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
                        Gtxn[tidx.load()].application_args[0] == portal_transfer_selector, # sha256("portal_transfer(byte[])byte[]")[:4]
                        Gtxn[tidx.load()].application_args[1] == Concat(Extract(Itob(Len(Txn.application_args[1])), Int(6), Int(2)), Txn.application_args[1]),
                        Gtxn[tidx.load()].application_id() == aid.load()
                    )),
                    Destination.store(getAppAddress(aid.load())),
                    Fee.store(Int(0))
                ]), Seq([
                    MagicAssert(Extract(Txn.application_args[1], off.load() + Int(101),Int(24)) == Extract(zb.load(), Int(0), Int(24))),
                    Fee.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(125),Int(8)))),  # uint256
                    MagicAssert(Fee.load() <= Amount.load()),

                    # Remove the fee
                    Amount.store(Amount.load() - Fee.load()),
                ])
            ),

            If(OriginChain.load() == Int(8),
               Seq([
                   asset.store(Btoi(Extract(Origin.load(), Int(24), Int(8)))),
                   MagicAssert(Txn.accounts[3] == get_sig_address(asset.load(), Bytes("native"))),
                   # Now, the horrible part... we have to scale the amount back out to compensate for the "dedusting" 
                   # when this was sent...

                   If(asset.load() == Int(0),
                      Seq([
                          InnerTxnBuilder.Begin(),
                          InnerTxnBuilder.SetFields(
                              {
                                  TxnField.sender: Txn.accounts[3],
                                  TxnField.type_enum: TxnType.Payment,
                                  TxnField.receiver: Destination.load(),
                                  TxnField.amount: Amount.load(),
                                  TxnField.fee: Int(0),
                              }
                          ),
                          If(Fee.load() > Int(0), Seq([
                                  InnerTxnBuilder.Next(),
                                  InnerTxnBuilder.SetFields(
                                      {
                                          TxnField.sender: Txn.accounts[3],
                                          TxnField.type_enum: TxnType.Payment,
                                          TxnField.receiver: Txn.sender(),
                                          TxnField.amount: Fee.load(),
                                          TxnField.fee: Int(0),
                                      }
                                  ),
                          ])),
                          InnerTxnBuilder.Submit(),

                          Approve()
                      ]),            # End of special case for algo
                      Seq([          # Start of handling code for algorand tokens
                          factor.store(getFactor(Btoi(extract_decimal(asset.load())))),
                          If(factor.load() != Int(1),
                             Seq([
                                 Amount.store(Amount.load() * factor.load()),
                                 Fee.store(Fee.load() * factor.load())
                             ])
                          ),       # If(factor.load() != Int(1),
                      ])           # End of handling code for algorand tokens
                   ),              # If(asset.load() == Int(0),
               ]),                 # If(OriginChain.load() == Int(8),

               # OriginChain.load() != Int(8),
               Seq([
                   # Lets see if we've seen this asset before
                   asset.store(Btoi(blob.read(Int(3), Int(0), Int(8)))),
                   MagicAssert(And(
                       asset.load() != Int(0),
                       Txn.accounts[3] == get_sig_address(OriginChain.load(), Origin.load())
                     )
                   ),
               ])  # OriginChain.load() != Int(8),
            ),  #  If(OriginChain.load() == Int(8)


            # Actually send the coins...
#            Log(Bytes("Main")),
            InnerTxnBuilder.Begin(),
            InnerTxnBuilder.SetFields(
                {
                    TxnField.sender: Txn.accounts[3],
                    TxnField.type_enum: TxnType.AssetTransfer,
                    TxnField.xfer_asset: asset.load(),
                    TxnField.asset_amount: Amount.load(),
                    TxnField.asset_receiver: Destination.load(),
                    TxnField.fee: Int(0),
                }
            ),

            If(Fee.load() > Int(0), Seq([
#                    Log(Bytes("Fees")),
                    InnerTxnBuilder.Next(),
                    InnerTxnBuilder.SetFields(
                        {
                            TxnField.sender: Txn.accounts[3],
                            TxnField.type_enum: TxnType.AssetTransfer,
                            TxnField.xfer_asset: asset.load(),
                            TxnField.asset_amount: Fee.load(),
                            TxnField.asset_receiver: Txn.sender(),
                            TxnField.fee: Int(0),
                        }
                    ),
            ])),
            InnerTxnBuilder.Submit(),

            Approve()
        ])

    METHOD = Txn.application_args[0]

    on_delete = Seq([Reject()])

    @Subroutine(TealType.bytes)
    def auth_addr(id) -> Expr:
        maybe = AccountParam.authAddr(id)
        return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))

    @Subroutine(TealType.bytes)
    def extract_name(id) -> Expr:
        maybe = AssetParam.name(id)
        return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))

    @Subroutine(TealType.bytes)
    def extract_creator(id) -> Expr:
        maybe = AssetParam.creator(id)
        return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))

    @Subroutine(TealType.bytes)
    def extract_unit_name(id) -> Expr:
        maybe = AssetParam.unitName(id)
        return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))

    @Subroutine(TealType.bytes)
    def extract_decimal(id) -> Expr:
        maybe = AssetParam.decimals(id)
        return Seq(maybe, If(maybe.hasValue(), Extract(Itob(maybe.value()), Int(7), Int(1)), Bytes("base16", "00")))


    def sendTransfer():
        aid = ScratchVar()
        amount = ScratchVar()
        d = ScratchVar()
        p = ScratchVar()
        asset = ScratchVar()
        aaddr = ScratchVar()
        Address = ScratchVar()
        FromChain = ScratchVar()
        zb = ScratchVar()
        factor = ScratchVar()
        fee = ScratchVar()

        return Seq([
            mfee.store(getMessageFee()),

            zb.store(BytesZero(Int(32))),

            aid.store(Btoi(Txn.application_args[1])),

            # what should we pass as a fee...
            fee.store(Btoi(Txn.application_args[5])),

            checkFeePmt(Int(2)),

            tidx.store(Txn.group_index() - Int(1)),

            If(aid.load() == Int(0),
               Seq([
                   MagicAssert(And(
                       # The previous txn is the asset transfer itself
                       Gtxn[tidx.load()].type_enum() == TxnType.Payment,
                       Gtxn[tidx.load()].sender() == Txn.sender(),
                       Gtxn[tidx.load()].receiver() == Txn.accounts[2],
                   )),
                   assert_common_checks(Gtxn[tidx.load()]),

                   amount.store(Gtxn[tidx.load()].amount()),

                   # fee cannot exceed amount
                   MagicAssert(fee.load() <= amount.load()),
               ]),
               Seq([
                   MagicAssert(And(
                       # The previous txn is the asset transfer itself
                       Gtxn[tidx.load()].type_enum() == TxnType.AssetTransfer,
                       Gtxn[tidx.load()].sender() == Txn.sender(),
                       Gtxn[tidx.load()].xfer_asset() == aid.load(),
                       Gtxn[tidx.load()].asset_receiver() == Txn.accounts[2],
                   )),
                   assert_common_checks(Gtxn[tidx.load()]),

                   amount.store(Gtxn[tidx.load()].asset_amount()),

                   #  fee cannot exceed amount
                   MagicAssert(fee.load() <= amount.load()),

                   factor.store(getFactor(Btoi(extract_decimal(aid.load())))),

                   If(factor.load() != Int(1),
                      Seq([
                          amount.store(amount.load() / factor.load()),
                          fee.store(fee.load() / factor.load()),
                      ])
                    ),       # If(factor.load() != Int(1),
               ]),
            ),


            # If it is nothing but dust lets just abort the whole transaction and save 
            MagicAssert(And(amount.load() > Int(0), fee.load() >= Int(0))),

            If(aid.load() != Int(0),
               aaddr.store(auth_addr(extract_creator(aid.load()))),
               aaddr.store(Bytes(""))),
            
            # Is the authorizing signature of the creator of the asset the address of the token_bridge app itself?
            If(aaddr.load() == Global.current_application_address(),
               Seq([
                   asset.store(blob.read(Int(2), Int(0), Int(8))),
                   # This the correct asset?
                   MagicAssert(Txn.application_args[1] == asset.load()),

                   # Pull the address and chain out of the original vaa
                   Address.store(blob.read(Int(2), Int(60), Int(92))),
                   FromChain.store(blob.read(Int(2), Int(92), Int(94))),

                   # This the correct page given the chain and the address
                   MagicAssert(Txn.accounts[2] == get_sig_address(Btoi(FromChain.load()), Address.load())),
               ]),
               Seq([
                   MagicAssert(Txn.accounts[2] == get_sig_address(aid.load(), Bytes("native"))),
                   FromChain.store(Bytes("base16", "0008")),
                   Address.store(Txn.application_args[1]),
               ])
            ),

            # Correct address len?
            MagicAssert(And(
                Len(Address.load()) <= Int(32),
                Len(FromChain.load()) == Int(2),
                Len(Txn.application_args[3]) <= Int(32)
            )),

            p.store(Concat(
                If(Txn.application_args.length() == Int(6),
                   Bytes("base16", "01"),
                   Bytes("base16", "03")),
                Extract(zb.load(), Int(0), Int(24)),
                Itob(amount.load()),  # 8 bytes
                Extract(zb.load(), Int(0), Int(32) - Len(Address.load())),
                Address.load(),
                FromChain.load(),
                Extract(zb.load(), Int(0), Int(32) - Len(Txn.application_args[3])),
                Txn.application_args[3],
                Extract(Txn.application_args[4], Int(6), Int(2)),
                If(Txn.application_args.length() == Int(7), Concat(Txn.sender(), Txn.application_args[6]), Concat(Extract(zb.load(), Int(0), Int(24)), Itob(fee.load())))
            )),

            # This one magic line should protect us from overruns/underruns and trickery..
            If(Txn.application_args.length() == Int(7), 
               MagicAssert(Len(p.load()) == Int(133) + Len(Txn.application_args[6])),
               MagicAssert(Len(p.load()) == Int(133))),

            InnerTxnBuilder.Begin(),
            sendMfee(),
            InnerTxnBuilder.SetFields(
                {
                    TxnField.type_enum: TxnType.ApplicationCall,
                    TxnField.application_id: App.globalGet(Bytes("coreid")),
                    TxnField.application_args: [Bytes("publishMessage"), p.load(), Itob(Int(0))],
                    TxnField.accounts: [Txn.accounts[1]],
                    TxnField.note: Bytes("publishMessage"),
                    TxnField.fee: Int(0),
                }
            ),
            InnerTxnBuilder.Submit(),

            Approve()
        ])

    def do_optin():
        return Seq([
            MagicAssert(Txn.accounts[1] == get_sig_address(Btoi(Txn.application_args[1]), Bytes("native"))),
            assert_common_checks(Txn),

            InnerTxnBuilder.Begin(),
            InnerTxnBuilder.SetFields(
                {
                    TxnField.sender: Txn.accounts[1],
                    TxnField.type_enum: TxnType.AssetTransfer,
                    TxnField.xfer_asset: Btoi(Txn.application_args[1]),
                    TxnField.asset_amount: Int(0),
                    TxnField.asset_receiver: Txn.accounts[1],
                    TxnField.fee: Int(0),
                }
            ),
            InnerTxnBuilder.Submit(),

            Approve()
        ])

    # This is for attesting
    def attestToken():
        asset = ScratchVar()
        p = ScratchVar()
        zb = ScratchVar()
        d = ScratchVar()
        uname = ScratchVar()
        name = ScratchVar()
        aid = ScratchVar()

        Address = ScratchVar()
        FromChain = ScratchVar()

        return Seq([
            mfee.store(getMessageFee()),

            checkFeePmt(Int(1)),

            aid.store(Btoi(Txn.application_args[1])),
            # Is the authorizing signature of the creator of the asset the address of the token_bridge app itself?
            If(If(aid.load() != Int(0), auth_addr(extract_creator(aid.load())) == Global.current_application_address(), Int(0)),
               Seq([
                   # Cannot attest a wormhole wrapped token
                   Reject()
               ]),
               Seq([
                   MagicAssert(Txn.accounts[2] == get_sig_address(aid.load(), Bytes("native"))),

                   zb.store(BytesZero(Int(32))),
                   
                   aid.store(Btoi(Txn.application_args[1])),

                   If(aid.load() == Int(0),
                      Seq([
                          d.store(Bytes("base16", "06")),
                          uname.store(Bytes("ALGO")),
                          name.store(Bytes("ALGO"))
                      ]),
                      Seq([
                          d.store(extract_decimal(aid.load())),
                          If(Btoi(d.load()) > Int(8), d.store(Bytes("base16", "08"))),
                          uname.store(extract_unit_name(aid.load())),
                          name.store(extract_name(aid.load())),
                      ])
                    ),

                   p.store(
                       Concat(
                           #PayloadID uint8 = 2
                           Bytes("base16", "02"),
                           #TokenAddress [32]uint8
                           Extract(zb.load(),Int(0), Int(24)),
                           Itob(aid.load()),
                           #TokenChain uint16
                           Bytes("base16", "0008"),
                           #Decimals uint8
                           d.load(),
                           #Symbol [32]uint8
                           uname.load(),
                           Extract(zb.load(), Int(0), Int(32) - Len(uname.load())),
                           #Name [32]uint8
                           name.load(),
                           Extract(zb.load(), Int(0), Int(32) - Len(name.load())),
                       )
                   ),
               ])
               ),

            MagicAssert(Len(p.load()) == Int(100)),

            InnerTxnBuilder.Begin(),
            sendMfee(),
            InnerTxnBuilder.SetFields(
                {
                    TxnField.type_enum: TxnType.ApplicationCall,
                    TxnField.application_id: App.globalGet(Bytes("coreid")),
                    TxnField.application_args: [Bytes("publishMessage"), p.load(), Itob(Int(0))],
                    TxnField.accounts: [Txn.accounts[1]],
                    TxnField.note: Bytes("publishMessage"),
                    TxnField.fee: Int(0),
                }
            ),
            InnerTxnBuilder.Submit(),

            Approve()
        ])

    @Subroutine(TealType.none)
    def checkForDuplicate():
        off = ScratchVar()
        emitter = ScratchVar()
        sequence = ScratchVar()
        b = ScratchVar()
        byte_offset = ScratchVar()

        return Seq(
            # VM only is version 1
            MagicAssert(Btoi(Extract(Txn.application_args[1], Int(0), Int(1))) == Int(1)),

            off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)), # The offset of the emitter

            # emitter is chain/contract-address
            emitter.store(Extract(Txn.application_args[1], off.load(), Int(34))),
            sequence.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(34), Int(8)))),

            # They passed us the correct account?  In this case, byte_offset points at the whole block
            byte_offset.store(sequence.load() / Int(max_bits)),
            MagicAssert(Txn.accounts[1] == get_sig_address(byte_offset.load(), emitter.load())),

            # Now, lets go grab the raw byte
            byte_offset.store((sequence.load() / Int(8)) % Int(max_bytes)),
            b.store(blob.get_byte(Int(1), byte_offset.load())),

            # I would hope we've never seen this packet before...   throw an exception if we have
            MagicAssert(GetBit(b.load(), sequence.load() % Int(8)) == Int(0)),

            # Lets mark this bit so that we never see it again
            blob.set_byte(Int(1), byte_offset.load(), SetBit(b.load(), sequence.load() % Int(8), Int(1)))
        )

    def nop():
        return Seq([Approve()])

    router = Cond(
        [METHOD == Bytes("nop"), nop()],
        [METHOD == Bytes("receiveAttest"), receiveAttest()],
        [METHOD == Bytes("attestToken"), attestToken()],
        [METHOD == Bytes("completeTransfer"), completeTransfer()],
        [METHOD == Bytes("sendTransfer"), sendTransfer()],
        [METHOD == Bytes("optin"), do_optin()],
        [METHOD == Bytes("governance"), governance()]
    )

    on_create = Seq( [
        App.globalPut(Bytes("coreid"), Btoi(Txn.application_args[0])),
        App.globalPut(Bytes("coreAddr"), Txn.application_args[1]),
        App.globalPut(Bytes("validUpdateApproveHash"), Bytes("")),
        App.globalPut(Bytes("validUpdateClearHash"), Bytes("base16", "73be5fd7cd378289177bf4a7ca5433ab30d91b417381bba8bd704aff2dec424f")), # empty clear state program
        Return(Int(1))
    ])

    def getOnUpdate():
        if devMode:
            return Seq( [
                Return(Txn.sender() == Global.creator_address()),
            ])
        else:
            return Seq( [
                MagicAssert(Sha512_256(Concat(Bytes("Program"), Txn.approval_program())) == App.globalGet(Bytes("validUpdateApproveHash"))),
                MagicAssert(Sha512_256(Concat(Bytes("Program"), Txn.clear_state_program())) == App.globalGet(Bytes("validUpdateClearHash"))),
                Return(Int(1))
            ] )

    on_update = getOnUpdate()

    @Subroutine(TealType.uint64)
    def optin():
        # Alias for readability
        algo_seed = Gtxn[Txn.group_index() - Int(1)]
        optin = Txn

        well_formed_optin = And(
            # Check that we're paying it
            algo_seed.type_enum() == TxnType.Payment,
            algo_seed.amount() == Int(seed_amt),
            # Check that its an opt in to us
            optin.type_enum() == TxnType.ApplicationCall,
            optin.on_completion() == OnComplete.OptIn
        )

        return Seq(
            # Make sure its a valid optin
            MagicAssert(well_formed_optin),
            # Init by writing to the full space available for the sender (Int(0))
            blob.zero(Int(0)),
            # we gucci
            Int(1)
        )

    on_optin = Seq( [
        Return(optin())
    ])

    return Cond(
        [Txn.application_id() == Int(0), on_create],
        [Txn.on_completion() == OnComplete.UpdateApplication, on_update],
        [Txn.on_completion() == OnComplete.DeleteApplication, on_delete],
        [Txn.on_completion() == OnComplete.OptIn, on_optin],
        [Txn.on_completion() == OnComplete.NoOp, router]
    )

def get_token_bridge(genTeal, approve_name, clear_name, client: AlgodClient, seed_amt: int, tmpl_sig: TmplSig, devMode: bool) -> Tuple[bytes, bytes]:
    if not devMode:
        client = AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "https://testnet-api.algonode.cloud")
    APPROVAL_PROGRAM = fullyCompileContract(genTeal, client, approve_token_bridge(seed_amt, tmpl_sig, devMode), approve_name, devMode)
    CLEAR_STATE_PROGRAM = fullyCompileContract(genTeal, client, clear_token_bridge(), clear_name, devMode)

    return APPROVAL_PROGRAM, CLEAR_STATE_PROGRAM
