token_bridge.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. #!/usr/bin/python3
  2. """
  3. Copyright 2022 Wormhole Project Contributors
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. """
  14. from typing import List, Tuple, Dict, Any, Optional, Union
  15. from pyteal.ast import *
  16. from pyteal.types import *
  17. from pyteal.compiler import *
  18. from pyteal.ir import *
  19. from globals import *
  20. from inlineasm import *
  21. from algosdk.v2client.algod import AlgodClient
  22. from algosdk.encoding import decode_address
  23. from TmplSig import TmplSig
  24. from local_blob import LocalBlob
  25. import pprint
  26. import sys
  27. max_keys = 15
  28. max_bytes_per_key = 127
  29. bits_per_byte = 8
  30. bits_per_key = max_bytes_per_key * bits_per_byte
  31. max_bytes = max_bytes_per_key * max_keys
  32. max_bits = bits_per_byte * max_bytes
  33. portal_transfer_selector = MethodSignature("portal_transfer(byte[])byte[]")
  34. def fullyCompileContract(genTeal, client: AlgodClient, contract: Expr, name, devmode) -> bytes:
  35. if devmode:
  36. teal = compileTeal(contract, mode=Mode.Application, version=6, assembleConstants=True)
  37. else:
  38. teal = compileTeal(contract, mode=Mode.Application, version=6, assembleConstants=True, optimize=OptimizeOptions(scratch_slots=True))
  39. if genTeal:
  40. with open(name, "w") as f:
  41. print("Writing " + name)
  42. f.write(teal)
  43. else:
  44. with open(name, "r") as f:
  45. print("Reading " + name)
  46. teal = f.read()
  47. response = client.compile(teal)
  48. with open(name + ".bin", "w") as fout:
  49. fout.write(response["result"])
  50. with open(name + ".hash", "w") as fout:
  51. fout.write(decode_address(response["hash"]).hex())
  52. return response
  53. def clear_token_bridge():
  54. return Int(1)
  55. def approve_token_bridge(seed_amt: int, tmpl_sig: TmplSig, devMode: bool):
  56. blob = LocalBlob()
  57. tidx = ScratchVar()
  58. mfee = ScratchVar()
  59. def MagicAssert(a) -> Expr:
  60. if devMode:
  61. from inspect import currentframe
  62. return Assert(And(a, Int(currentframe().f_back.f_lineno)))
  63. else:
  64. return Assert(a)
  65. @Subroutine(TealType.uint64)
  66. def governanceSet() -> Expr:
  67. maybe = App.globalGetEx(App.globalGet(Bytes("coreid")), Bytes("currentGuardianSetIndex"))
  68. return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())
  69. @Subroutine(TealType.uint64)
  70. def getMessageFee() -> Expr:
  71. maybe = App.globalGetEx(App.globalGet(Bytes("coreid")), Bytes("MessageFee"))
  72. return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())
  73. @Subroutine(TealType.bytes)
  74. def getAppAddress(appid : Expr) -> Expr:
  75. maybe = AppParam.address(appid)
  76. return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())
  77. def assert_common_checks(e) -> Expr:
  78. return MagicAssert(And(
  79. e.rekey_to() == Global.zero_address(),
  80. e.close_remainder_to() == Global.zero_address(),
  81. e.asset_close_to() == Global.zero_address(),
  82. e.on_completion() == OnComplete.NoOp
  83. ))
  84. @Subroutine(TealType.none)
  85. def checkFeePmt(off : Expr):
  86. return Seq([
  87. If(mfee.load() > Int(0), Seq([
  88. tidx.store(Txn.group_index() - off),
  89. MagicAssert(And(
  90. Gtxn[tidx.load()].type_enum() == TxnType.Payment,
  91. Gtxn[tidx.load()].sender() == Txn.sender(),
  92. Gtxn[tidx.load()].receiver() == Global.current_application_address(),
  93. Gtxn[tidx.load()].amount() >= mfee.load()
  94. )),
  95. assert_common_checks(Gtxn[tidx.load()])
  96. ]))
  97. ])
  98. @Subroutine(TealType.none)
  99. def sendMfee():
  100. return Seq([
  101. If (mfee.load() > Int(0), Seq([
  102. InnerTxnBuilder.SetFields(
  103. {
  104. TxnField.type_enum: TxnType.Payment,
  105. TxnField.receiver: App.globalGet(Bytes("coreAddr")),
  106. TxnField.amount: mfee.load(),
  107. TxnField.fee: Int(0),
  108. }
  109. ),
  110. InnerTxnBuilder.Next(),
  111. ])),
  112. ])
  113. @Subroutine(TealType.bytes)
  114. def encode_uvarint(val: Expr, b: Expr):
  115. buff = ScratchVar()
  116. return Seq(
  117. buff.store(b),
  118. Concat(
  119. buff.load(),
  120. If(
  121. val >= Int(128),
  122. encode_uvarint(
  123. val >> Int(7),
  124. Extract(Itob((val & Int(255)) | Int(128)), Int(7), Int(1)),
  125. ),
  126. Extract(Itob(val & Int(255)), Int(7), Int(1)),
  127. ),
  128. ),
  129. )
  130. @Subroutine(TealType.bytes)
  131. def trim_bytes(str: Expr):
  132. len = ScratchVar()
  133. off = ScratchVar()
  134. zero = ScratchVar()
  135. r = ScratchVar()
  136. return Seq([
  137. r.store(str),
  138. len.store(Len(r.load())),
  139. zero.store(BytesZero(Int(1))),
  140. off.store(Int(0)),
  141. While(off.load() < len.load()).Do(Seq([
  142. If(Extract(r.load(), off.load(), Int(1)) == zero.load()).Then(Seq([
  143. r.store(Extract(r.load(), Int(0), off.load())),
  144. off.store(len.load())
  145. ])),
  146. off.store(off.load() + Int(1))
  147. ])),
  148. r.load()
  149. ])
  150. @Subroutine(TealType.uint64)
  151. def getFactor(dec: Expr):
  152. return Cond(
  153. [dec < Int(9), Int(1)],
  154. [dec > Int(19), Seq(Reject(), Int(1))],
  155. [Int(1), Exp(Int(10), dec - Int(8))]
  156. )
  157. @Subroutine(TealType.bytes)
  158. def get_sig_address(acct_seq_start: Expr, emitter: Expr):
  159. # We could iterate over N items and encode them for a more general interface
  160. # but we inline them directly here
  161. return Sha512_256(
  162. Concat(
  163. Bytes("Program"),
  164. # ADDR_IDX aka sequence start
  165. tmpl_sig.get_bytecode_chunk(0),
  166. encode_uvarint(acct_seq_start, Bytes("")),
  167. # EMITTER_ID
  168. tmpl_sig.get_bytecode_chunk(1),
  169. encode_uvarint(Len(emitter), Bytes("")),
  170. emitter,
  171. # APP_ID
  172. tmpl_sig.get_bytecode_chunk(2),
  173. encode_uvarint(Global.current_application_id(), Bytes("")),
  174. # TMPL_APP_ADDRESS
  175. tmpl_sig.get_bytecode_chunk(3),
  176. encode_uvarint(Len(Global.current_application_address()), Bytes("")),
  177. Global.current_application_address(),
  178. tmpl_sig.get_bytecode_chunk(4),
  179. )
  180. )
  181. def governance():
  182. off = ScratchVar()
  183. a = ScratchVar()
  184. targetChain = ScratchVar()
  185. chain = ScratchVar()
  186. emitter = ScratchVar()
  187. set = ScratchVar()
  188. idx = ScratchVar()
  189. verifyIdx = ScratchVar()
  190. verifyVAA = Gtxn[verifyIdx.load()]
  191. return Seq([
  192. checkForDuplicate(),
  193. # All governance must be done with the most recent guardian set...
  194. set.store(governanceSet()),
  195. idx.store(Extract(Txn.application_args[1], Int(1), Int(4))),
  196. MagicAssert(Btoi(idx.load()) == set.load()),
  197. # The offset of the chain
  198. off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)),
  199. verifyIdx.store(Txn.group_index() - Int(1)),
  200. MagicAssert(And(
  201. # Did verifyVAA pass?
  202. verifyVAA.type_enum() == TxnType.ApplicationCall,
  203. verifyVAA.application_id() == App.globalGet(Bytes("coreid")),
  204. verifyVAA.application_args[0] == Bytes("verifyVAA"),
  205. verifyVAA.sender() == Txn.sender(),
  206. verifyVAA.on_completion() == OnComplete.NoOp,
  207. # Lets see if the vaa we are about to process was actually verified by the core
  208. verifyVAA.application_args[1] == Txn.application_args[1],
  209. # We all opted into the same accounts?
  210. verifyVAA.accounts[0] == Txn.accounts[0],
  211. verifyVAA.accounts[1] == Txn.accounts[1],
  212. verifyVAA.accounts[2] == Txn.accounts[2],
  213. # Better be the right emitters
  214. Extract(Txn.application_args[1], off.load(), Int(2)) == Bytes("base16", "0001"),
  215. Extract(Txn.application_args[1], off.load() + Int(2), Int(32)) == Concat(BytesZero(Int(31)), Bytes("base16", "04")),
  216. )),
  217. assert_common_checks(verifyVAA),
  218. assert_common_checks(Txn),
  219. # correct module?
  220. MagicAssert(Extract(Txn.application_args[1], off.load() + Int(43), Int(32)) == Concat(BytesZero(Int(21)), Bytes("base16", "546f6b656e427269646765"))),
  221. a.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(75), Int(1)))),
  222. off.store(off.load() + Int(76)),
  223. Cond(
  224. [a.load() == Int(1), Seq([
  225. targetChain.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(2)))),
  226. MagicAssert(Or((targetChain.load() == Int(0)), (targetChain.load() == Int(8)))),
  227. chain.store(Extract(Txn.application_args[1], off.load() + Int(2), Int(2))),
  228. emitter.store(Extract(Txn.application_args[1], off.load() + Int(4), Int(32))),
  229. # Can I only register once? Rumor says yes
  230. MagicAssert(App.globalGet(Concat(Bytes("Chain"), chain.load())) == Int(0)),
  231. App.globalPut(Concat(Bytes("Chain"), chain.load()), emitter.load()),
  232. ])],
  233. [a.load() == Int(2), Seq([
  234. MagicAssert(Extract(Txn.application_args[1], off.load(), Int(2)) == Bytes("base16", "0008")),
  235. App.globalPut(Bytes("validUpdateApproveHash"), Extract(Txn.application_args[1], off.load() + Int(2), Int(32)))
  236. ])]
  237. ),
  238. Approve()
  239. ])
  240. # # This blows up an asset on algorand. This will be added temporarily (and then removed) to clean some stuff before we relaunch
  241. # def killAsset():
  242. # return Seq([
  243. # MagicAssert(Txn.sender() == Global.creator_address()),
  244. #
  245. # blob.zero(Int(1)),
  246. #
  247. # InnerTxnBuilder.Begin(),
  248. # InnerTxnBuilder.SetFields(
  249. # {
  250. # TxnField.sender: Global.current_application_address(),
  251. # TxnField.type_enum: TxnType.AssetConfig,
  252. # TxnField.config_asset: Btoi(Txn.application_args[1]),
  253. # TxnField.fee: Int(0),
  254. # }
  255. # ),
  256. # InnerTxnBuilder.Submit(),
  257. #
  258. # Approve()
  259. # ])
  260. def receiveAttest():
  261. me = Global.current_application_address()
  262. off = ScratchVar()
  263. Address = ScratchVar()
  264. Chain = ScratchVar()
  265. FromChain = ScratchVar()
  266. Decimals = ScratchVar()
  267. Symbol = ScratchVar()
  268. Name = ScratchVar()
  269. asset = ScratchVar()
  270. buf = ScratchVar()
  271. c = ScratchVar()
  272. a = ScratchVar()
  273. return Seq([
  274. checkForDuplicate(),
  275. tidx.store(Txn.group_index() - Int(4)),
  276. MagicAssert(And(
  277. # Lets see if the vaa we are about to process was actually verified by the core
  278. Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
  279. Gtxn[tidx.load()].application_id() == App.globalGet(Bytes("coreid")),
  280. Gtxn[tidx.load()].application_args[0] == Bytes("verifyVAA"),
  281. Gtxn[tidx.load()].sender() == Txn.sender(),
  282. Gtxn[tidx.load()].on_completion() == OnComplete.NoOp,
  283. # we are all taking about the same vaa?
  284. Gtxn[tidx.load()].application_args[1] == Txn.application_args[1],
  285. # We all opted into the same accounts?
  286. Gtxn[tidx.load()].accounts[0] == Txn.accounts[0],
  287. Gtxn[tidx.load()].accounts[1] == Txn.accounts[1],
  288. Gtxn[tidx.load()].accounts[2] == Txn.accounts[2],
  289. )),
  290. assert_common_checks(Gtxn[tidx.load()]),
  291. tidx.store(Txn.group_index() - Int(3)),
  292. MagicAssert(And(
  293. # Did the user pay the lsig to attest a new product?
  294. Gtxn[tidx.load()].type_enum() == TxnType.Payment,
  295. Gtxn[tidx.load()].amount() >= Int(100000),
  296. Gtxn[tidx.load()].sender() == Txn.sender(),
  297. Gtxn[tidx.load()].receiver() == Txn.accounts[3],
  298. )),
  299. assert_common_checks(Gtxn[tidx.load()]),
  300. tidx.store(Txn.group_index() - Int(2)),
  301. MagicAssert(And(
  302. # We had to buy some extra CPU
  303. Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
  304. Gtxn[tidx.load()].application_id() == Global.current_application_id(),
  305. Gtxn[tidx.load()].application_args[0] == Bytes("nop"),
  306. Gtxn[tidx.load()].sender() == Txn.sender(),
  307. )),
  308. assert_common_checks(Gtxn[tidx.load()]),
  309. tidx.store(Txn.group_index() - Int(1)),
  310. MagicAssert(And(
  311. Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
  312. Gtxn[tidx.load()].application_id() == Global.current_application_id(),
  313. Gtxn[tidx.load()].application_args[0] == Bytes("nop"),
  314. Gtxn[tidx.load()].sender() == Txn.sender(),
  315. (Global.group_size() - Int(1)) == Txn.group_index() # This should be the last entry...
  316. )),
  317. assert_common_checks(Gtxn[tidx.load()]),
  318. off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(6) + Int(8)), # The offset of the chain
  319. Chain.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(2)))),
  320. # Make sure that the emitter on the sending chain is correct for the token bridge
  321. MagicAssert(App.globalGet(Concat(Bytes("Chain"), Extract(Txn.application_args[1], off.load(), Int(2))))
  322. == Extract(Txn.application_args[1], off.load() + Int(2), Int(32))),
  323. off.store(off.load()+Int(43)),
  324. MagicAssert(Int(2) == Btoi(Extract(Txn.application_args[1], off.load(), Int(1)))),
  325. Address.store( Extract(Txn.application_args[1], off.load() + Int(1), Int(32))),
  326. FromChain.store( Btoi(Extract(Txn.application_args[1], off.load() + Int(33), Int(2)))),
  327. Decimals.store( Btoi(Extract(Txn.application_args[1], off.load() + Int(35), Int(1)))),
  328. Symbol.store( Extract(Txn.application_args[1], off.load() + Int(36), Int(32))),
  329. Name.store( Extract(Txn.application_args[1], off.load() + Int(68), Int(32))),
  330. # Lets trim this... seems these are limited to 8 characters
  331. Symbol.store(trim_bytes(Symbol.load())),
  332. If (Len(Symbol.load()) > Int(8), Symbol.store(Extract(Symbol.load(), Int(0), Int(8)))),
  333. Name.store(trim_bytes(Name.load())),
  334. # Due to constrains on some supported chains, all token
  335. # amounts passed through the token bridge are truncated to
  336. # a maximum of 8 decimals.
  337. #
  338. # Any chains implementation must make sure that of any
  339. # token only ever MaxUint64 units (post-shifting) are
  340. # bridged into the wormhole network at any given time (all
  341. # target chains combined), even tough the slot is 32 bytes
  342. # long (theoretically fitting uint256).
  343. If(Decimals.load() > Int(8), Decimals.store(Int(8))),
  344. # This confirms the user gave us access to the correct memory for this asset..
  345. MagicAssert(Txn.accounts[3] == get_sig_address(FromChain.load(), Address.load())),
  346. # Lets see if we've seen this asset before
  347. asset.store(blob.read(Int(3), Int(0), Int(8))),
  348. # The # offset to the digest
  349. off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(6)),
  350. # New asset
  351. If(asset.load() == Itob(Int(0))).Then(Seq([
  352. InnerTxnBuilder.Begin(),
  353. InnerTxnBuilder.SetFields(
  354. {
  355. TxnField.sender: Txn.accounts[3],
  356. TxnField.type_enum: TxnType.AssetConfig,
  357. TxnField.config_asset_name: Name.load(),
  358. TxnField.config_asset_unit_name: Symbol.load(),
  359. TxnField.config_asset_total: Int(18446744073709550000),
  360. TxnField.config_asset_decimals: Decimals.load(),
  361. TxnField.config_asset_manager: me,
  362. TxnField.config_asset_reserve: Txn.accounts[3],
  363. TxnField.config_asset_freeze: me,
  364. TxnField.config_asset_clawback: me,
  365. TxnField.fee: Int(0),
  366. }
  367. ),
  368. InnerTxnBuilder.Submit(),
  369. asset.store(Itob(InnerTxn.created_asset_id())),
  370. Pop(blob.write(Int(3), Int(0), asset.load())),
  371. blob.meta(Int(3), Bytes("asset"))
  372. ])),
  373. # We save away the entire digest that created this asset in case we ever need to reproduce it while sending this
  374. # coin to another chain
  375. buf.store(Txn.application_args[1]),
  376. Pop(blob.write(Int(3), Int(8), Extract(buf.load(), off.load(), Len(buf.load()) - off.load()))),
  377. Approve()
  378. ])
  379. def completeTransfer():
  380. me = Global.current_application_address()
  381. off = ScratchVar()
  382. Chain = ScratchVar()
  383. Emitter = ScratchVar()
  384. Amount = ScratchVar()
  385. Origin = ScratchVar()
  386. OriginChain = ScratchVar()
  387. Destination = ScratchVar()
  388. DestChain = ScratchVar()
  389. Fee = ScratchVar()
  390. asset = ScratchVar()
  391. factor = ScratchVar()
  392. d = ScratchVar()
  393. zb = ScratchVar()
  394. action = ScratchVar()
  395. aid = ScratchVar()
  396. return Seq([
  397. checkForDuplicate(),
  398. zb.store(BytesZero(Int(32))),
  399. tidx.store(Txn.group_index() - Int(1)),
  400. MagicAssert(And(
  401. # Lets see if the vaa we are about to process was actually verified by the core
  402. Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
  403. Gtxn[tidx.load()].application_id() == App.globalGet(Bytes("coreid")),
  404. Gtxn[tidx.load()].application_args[0] == Bytes("verifyVAA"),
  405. Gtxn[tidx.load()].sender() == Txn.sender(),
  406. Gtxn[tidx.load()].on_completion() == OnComplete.NoOp,
  407. # Lets see if the vaa we are about to process was actually verified by the core
  408. Gtxn[tidx.load()].application_args[1] == Txn.application_args[1],
  409. # We all opted into the same accounts?
  410. Gtxn[tidx.load()].accounts[0] == Txn.accounts[0],
  411. Gtxn[tidx.load()].accounts[1] == Txn.accounts[1],
  412. Gtxn[tidx.load()].accounts[2] == Txn.accounts[2]
  413. )),
  414. assert_common_checks(Gtxn[tidx.load()]),
  415. assert_common_checks(Txn),
  416. off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(6) + Int(8)), # The offset of the chain
  417. Chain.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(2)))),
  418. Emitter.store(Extract(Txn.application_args[1], off.load() + Int(2), Int(32))),
  419. # We coming from the correct emitter on the sending chain for the token bridge
  420. # ... This is 90% of the security...
  421. If(Chain.load() == Int(8),
  422. MagicAssert(Global.current_application_address() == Emitter.load()), # This came from us?
  423. MagicAssert(App.globalGet(Concat(Bytes("Chain"), Extract(Txn.application_args[1], off.load(), Int(2)))) == Emitter.load())),
  424. off.store(off.load()+Int(43)),
  425. # This is a transfer message... right?
  426. action.store(Btoi(Extract(Txn.application_args[1], off.load(), Int(1)))),
  427. MagicAssert(Or(action.load() == Int(1), action.load() == Int(3))),
  428. MagicAssert(Extract(Txn.application_args[1], off.load() + Int(1), Int(24)) == Extract(zb.load(), Int(0), Int(24))),
  429. Amount.store( Btoi(Extract(Txn.application_args[1], off.load() + Int(25), Int(8)))), # uint256
  430. Origin.store( Extract(Txn.application_args[1], off.load() + Int(33), Int(32))),
  431. OriginChain.store( Btoi(Extract(Txn.application_args[1], off.load() + Int(65), Int(2)))),
  432. Destination.store( Extract(Txn.application_args[1], off.load() + Int(67), Int(32))),
  433. DestChain.store( Btoi(Extract(Txn.application_args[1], off.load() + Int(99), Int(2)))),
  434. # This directed at us?
  435. MagicAssert(DestChain.load() == Int(8)),
  436. If (action.load() == Int(3), Seq([
  437. aid.store(Btoi(Extract(Destination.load(), Int(24), Int(8)))), # The destination is the appid in a payload3
  438. tidx.store(Txn.group_index() + Int(1)),
  439. MagicAssert(And(
  440. Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
  441. Gtxn[tidx.load()].application_args[0] == portal_transfer_selector, # sha256("portal_transfer(byte[])byte[]")[:4]
  442. Gtxn[tidx.load()].application_args[1] == Concat(Extract(Itob(Len(Txn.application_args[1])), Int(6), Int(2)), Txn.application_args[1]),
  443. Gtxn[tidx.load()].application_id() == aid.load()
  444. )),
  445. Destination.store(getAppAddress(aid.load())),
  446. Fee.store(Int(0))
  447. ]), Seq([
  448. MagicAssert(Extract(Txn.application_args[1], off.load() + Int(101),Int(24)) == Extract(zb.load(), Int(0), Int(24))),
  449. Fee.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(125),Int(8)))), # uint256
  450. MagicAssert(Fee.load() <= Amount.load()),
  451. # Remove the fee
  452. Amount.store(Amount.load() - Fee.load()),
  453. ])
  454. ),
  455. If(OriginChain.load() == Int(8),
  456. Seq([
  457. asset.store(Btoi(Extract(Origin.load(), Int(24), Int(8)))),
  458. MagicAssert(Txn.accounts[3] == get_sig_address(asset.load(), Bytes("native"))),
  459. # Now, the horrible part... we have to scale the amount back out to compensate for the "dedusting"
  460. # when this was sent...
  461. If(asset.load() == Int(0),
  462. Seq([
  463. InnerTxnBuilder.Begin(),
  464. InnerTxnBuilder.SetFields(
  465. {
  466. TxnField.sender: Txn.accounts[3],
  467. TxnField.type_enum: TxnType.Payment,
  468. TxnField.receiver: Destination.load(),
  469. TxnField.amount: Amount.load(),
  470. TxnField.fee: Int(0),
  471. }
  472. ),
  473. If(Fee.load() > Int(0), Seq([
  474. InnerTxnBuilder.Next(),
  475. InnerTxnBuilder.SetFields(
  476. {
  477. TxnField.sender: Txn.accounts[3],
  478. TxnField.type_enum: TxnType.Payment,
  479. TxnField.receiver: Txn.sender(),
  480. TxnField.amount: Fee.load(),
  481. TxnField.fee: Int(0),
  482. }
  483. ),
  484. ])),
  485. InnerTxnBuilder.Submit(),
  486. Approve()
  487. ]), # End of special case for algo
  488. Seq([ # Start of handling code for algorand tokens
  489. factor.store(getFactor(Btoi(extract_decimal(asset.load())))),
  490. If(factor.load() != Int(1),
  491. Seq([
  492. Amount.store(Amount.load() * factor.load()),
  493. Fee.store(Fee.load() * factor.load())
  494. ])
  495. ), # If(factor.load() != Int(1),
  496. ]) # End of handling code for algorand tokens
  497. ), # If(asset.load() == Int(0),
  498. ]), # If(OriginChain.load() == Int(8),
  499. # OriginChain.load() != Int(8),
  500. Seq([
  501. # Lets see if we've seen this asset before
  502. asset.store(Btoi(blob.read(Int(3), Int(0), Int(8)))),
  503. MagicAssert(And(
  504. asset.load() != Int(0),
  505. Txn.accounts[3] == get_sig_address(OriginChain.load(), Origin.load())
  506. )
  507. ),
  508. ]) # OriginChain.load() != Int(8),
  509. ), # If(OriginChain.load() == Int(8)
  510. # Actually send the coins...
  511. # Log(Bytes("Main")),
  512. InnerTxnBuilder.Begin(),
  513. InnerTxnBuilder.SetFields(
  514. {
  515. TxnField.sender: Txn.accounts[3],
  516. TxnField.type_enum: TxnType.AssetTransfer,
  517. TxnField.xfer_asset: asset.load(),
  518. TxnField.asset_amount: Amount.load(),
  519. TxnField.asset_receiver: Destination.load(),
  520. TxnField.fee: Int(0),
  521. }
  522. ),
  523. If(Fee.load() > Int(0), Seq([
  524. # Log(Bytes("Fees")),
  525. InnerTxnBuilder.Next(),
  526. InnerTxnBuilder.SetFields(
  527. {
  528. TxnField.sender: Txn.accounts[3],
  529. TxnField.type_enum: TxnType.AssetTransfer,
  530. TxnField.xfer_asset: asset.load(),
  531. TxnField.asset_amount: Fee.load(),
  532. TxnField.asset_receiver: Txn.sender(),
  533. TxnField.fee: Int(0),
  534. }
  535. ),
  536. ])),
  537. InnerTxnBuilder.Submit(),
  538. Approve()
  539. ])
  540. METHOD = Txn.application_args[0]
  541. on_delete = Seq([Reject()])
  542. @Subroutine(TealType.bytes)
  543. def auth_addr(id) -> Expr:
  544. maybe = AccountParam.authAddr(id)
  545. return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))
  546. @Subroutine(TealType.bytes)
  547. def extract_name(id) -> Expr:
  548. maybe = AssetParam.name(id)
  549. return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))
  550. @Subroutine(TealType.bytes)
  551. def extract_creator(id) -> Expr:
  552. maybe = AssetParam.creator(id)
  553. return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))
  554. @Subroutine(TealType.bytes)
  555. def extract_unit_name(id) -> Expr:
  556. maybe = AssetParam.unitName(id)
  557. return Seq(maybe, If(maybe.hasValue(), maybe.value(), Bytes("")))
  558. @Subroutine(TealType.bytes)
  559. def extract_decimal(id) -> Expr:
  560. maybe = AssetParam.decimals(id)
  561. return Seq(maybe, If(maybe.hasValue(), Extract(Itob(maybe.value()), Int(7), Int(1)), Bytes("base16", "00")))
  562. def sendTransfer():
  563. aid = ScratchVar()
  564. amount = ScratchVar()
  565. d = ScratchVar()
  566. p = ScratchVar()
  567. asset = ScratchVar()
  568. aaddr = ScratchVar()
  569. Address = ScratchVar()
  570. FromChain = ScratchVar()
  571. zb = ScratchVar()
  572. factor = ScratchVar()
  573. fee = ScratchVar()
  574. return Seq([
  575. mfee.store(getMessageFee()),
  576. zb.store(BytesZero(Int(32))),
  577. aid.store(Btoi(Txn.application_args[1])),
  578. # what should we pass as a fee...
  579. fee.store(Btoi(Txn.application_args[5])),
  580. checkFeePmt(Int(2)),
  581. tidx.store(Txn.group_index() - Int(1)),
  582. If(aid.load() == Int(0),
  583. Seq([
  584. MagicAssert(And(
  585. # The previous txn is the asset transfer itself
  586. Gtxn[tidx.load()].type_enum() == TxnType.Payment,
  587. Gtxn[tidx.load()].sender() == Txn.sender(),
  588. Gtxn[tidx.load()].receiver() == Txn.accounts[2],
  589. )),
  590. assert_common_checks(Gtxn[tidx.load()]),
  591. amount.store(Gtxn[tidx.load()].amount()),
  592. # fee cannot exceed amount
  593. MagicAssert(fee.load() <= amount.load()),
  594. ]),
  595. Seq([
  596. MagicAssert(And(
  597. # The previous txn is the asset transfer itself
  598. Gtxn[tidx.load()].type_enum() == TxnType.AssetTransfer,
  599. Gtxn[tidx.load()].sender() == Txn.sender(),
  600. Gtxn[tidx.load()].xfer_asset() == aid.load(),
  601. Gtxn[tidx.load()].asset_receiver() == Txn.accounts[2],
  602. )),
  603. assert_common_checks(Gtxn[tidx.load()]),
  604. amount.store(Gtxn[tidx.load()].asset_amount()),
  605. # fee cannot exceed amount
  606. MagicAssert(fee.load() <= amount.load()),
  607. factor.store(getFactor(Btoi(extract_decimal(aid.load())))),
  608. If(factor.load() != Int(1),
  609. Seq([
  610. amount.store(amount.load() / factor.load()),
  611. fee.store(fee.load() / factor.load()),
  612. ])
  613. ), # If(factor.load() != Int(1),
  614. ]),
  615. ),
  616. # If it is nothing but dust lets just abort the whole transaction and save
  617. MagicAssert(And(amount.load() > Int(0), fee.load() >= Int(0))),
  618. If(aid.load() != Int(0),
  619. aaddr.store(auth_addr(extract_creator(aid.load()))),
  620. aaddr.store(Bytes(""))),
  621. # Is the authorizing signature of the creator of the asset the address of the token_bridge app itself?
  622. If(aaddr.load() == Global.current_application_address(),
  623. Seq([
  624. asset.store(blob.read(Int(2), Int(0), Int(8))),
  625. # This the correct asset?
  626. MagicAssert(Txn.application_args[1] == asset.load()),
  627. # Pull the address and chain out of the original vaa
  628. Address.store(blob.read(Int(2), Int(60), Int(92))),
  629. FromChain.store(blob.read(Int(2), Int(92), Int(94))),
  630. # This the correct page given the chain and the address
  631. MagicAssert(Txn.accounts[2] == get_sig_address(Btoi(FromChain.load()), Address.load())),
  632. ]),
  633. Seq([
  634. MagicAssert(Txn.accounts[2] == get_sig_address(aid.load(), Bytes("native"))),
  635. FromChain.store(Bytes("base16", "0008")),
  636. Address.store(Txn.application_args[1]),
  637. ])
  638. ),
  639. # Correct address len?
  640. MagicAssert(And(
  641. Len(Address.load()) <= Int(32),
  642. Len(FromChain.load()) == Int(2),
  643. Len(Txn.application_args[3]) <= Int(32),
  644. Txn.application_args.length() <= Int(7)
  645. )),
  646. p.store(Concat(
  647. If(Txn.application_args.length() == Int(7),
  648. Bytes("base16", "03"),
  649. Bytes("base16", "01")),
  650. Extract(zb.load(), Int(0), Int(24)),
  651. Itob(amount.load()), # 8 bytes
  652. Extract(zb.load(), Int(0), Int(32) - Len(Address.load())),
  653. Address.load(),
  654. FromChain.load(),
  655. Extract(zb.load(), Int(0), Int(32) - Len(Txn.application_args[3])),
  656. Txn.application_args[3],
  657. Extract(Txn.application_args[4], Int(6), Int(2)),
  658. 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())))
  659. )),
  660. # This one magic line should protect us from overruns/underruns and trickery..
  661. If(Txn.application_args.length() == Int(7),
  662. MagicAssert(Len(p.load()) == Int(133) + Len(Txn.application_args[6])),
  663. MagicAssert(Len(p.load()) == Int(133))),
  664. InnerTxnBuilder.Begin(),
  665. sendMfee(),
  666. InnerTxnBuilder.SetFields(
  667. {
  668. TxnField.type_enum: TxnType.ApplicationCall,
  669. TxnField.application_id: App.globalGet(Bytes("coreid")),
  670. TxnField.application_args: [Bytes("publishMessage"), p.load(), Itob(Int(0))],
  671. TxnField.accounts: [Txn.accounts[1]],
  672. TxnField.note: Bytes("publishMessage"),
  673. TxnField.fee: Int(0),
  674. }
  675. ),
  676. InnerTxnBuilder.Submit(),
  677. Approve()
  678. ])
  679. def do_optin():
  680. return Seq([
  681. MagicAssert(Txn.accounts[1] == get_sig_address(Btoi(Txn.application_args[1]), Bytes("native"))),
  682. assert_common_checks(Txn),
  683. InnerTxnBuilder.Begin(),
  684. InnerTxnBuilder.SetFields(
  685. {
  686. TxnField.sender: Txn.accounts[1],
  687. TxnField.type_enum: TxnType.AssetTransfer,
  688. TxnField.xfer_asset: Btoi(Txn.application_args[1]),
  689. TxnField.asset_amount: Int(0),
  690. TxnField.asset_receiver: Txn.accounts[1],
  691. TxnField.fee: Int(0),
  692. }
  693. ),
  694. InnerTxnBuilder.Submit(),
  695. Approve()
  696. ])
  697. # This is for attesting
  698. def attestToken():
  699. asset = ScratchVar()
  700. p = ScratchVar()
  701. zb = ScratchVar()
  702. d = ScratchVar()
  703. uname = ScratchVar()
  704. name = ScratchVar()
  705. aid = ScratchVar()
  706. Address = ScratchVar()
  707. FromChain = ScratchVar()
  708. return Seq([
  709. mfee.store(getMessageFee()),
  710. checkFeePmt(Int(1)),
  711. aid.store(Btoi(Txn.application_args[1])),
  712. # Is the authorizing signature of the creator of the asset the address of the token_bridge app itself?
  713. If(If(aid.load() != Int(0), auth_addr(extract_creator(aid.load())) == Global.current_application_address(), Int(0)),
  714. Seq([
  715. # Cannot attest a wormhole wrapped token
  716. Reject()
  717. ]),
  718. Seq([
  719. MagicAssert(Txn.accounts[2] == get_sig_address(aid.load(), Bytes("native"))),
  720. zb.store(BytesZero(Int(32))),
  721. aid.store(Btoi(Txn.application_args[1])),
  722. If(aid.load() == Int(0),
  723. Seq([
  724. d.store(Bytes("base16", "06")),
  725. uname.store(Bytes("ALGO")),
  726. name.store(Bytes("ALGO"))
  727. ]),
  728. Seq([
  729. d.store(extract_decimal(aid.load())),
  730. If(Btoi(d.load()) > Int(8), d.store(Bytes("base16", "08"))),
  731. uname.store(extract_unit_name(aid.load())),
  732. name.store(extract_name(aid.load())),
  733. ])
  734. ),
  735. p.store(
  736. Concat(
  737. #PayloadID uint8 = 2
  738. Bytes("base16", "02"),
  739. #TokenAddress [32]uint8
  740. Extract(zb.load(),Int(0), Int(24)),
  741. Itob(aid.load()),
  742. #TokenChain uint16
  743. Bytes("base16", "0008"),
  744. #Decimals uint8
  745. d.load(),
  746. #Symbol [32]uint8
  747. uname.load(),
  748. Extract(zb.load(), Int(0), Int(32) - Len(uname.load())),
  749. #Name [32]uint8
  750. name.load(),
  751. Extract(zb.load(), Int(0), Int(32) - Len(name.load())),
  752. )
  753. ),
  754. ])
  755. ),
  756. MagicAssert(Len(p.load()) == Int(100)),
  757. InnerTxnBuilder.Begin(),
  758. sendMfee(),
  759. InnerTxnBuilder.SetFields(
  760. {
  761. TxnField.type_enum: TxnType.ApplicationCall,
  762. TxnField.application_id: App.globalGet(Bytes("coreid")),
  763. TxnField.application_args: [Bytes("publishMessage"), p.load(), Itob(Int(0))],
  764. TxnField.accounts: [Txn.accounts[1]],
  765. TxnField.note: Bytes("publishMessage"),
  766. TxnField.fee: Int(0),
  767. }
  768. ),
  769. InnerTxnBuilder.Submit(),
  770. Approve()
  771. ])
  772. @Subroutine(TealType.none)
  773. def checkForDuplicate():
  774. off = ScratchVar()
  775. emitter = ScratchVar()
  776. sequence = ScratchVar()
  777. b = ScratchVar()
  778. byte_offset = ScratchVar()
  779. return Seq(
  780. # VM only is version 1
  781. MagicAssert(Btoi(Extract(Txn.application_args[1], Int(0), Int(1))) == Int(1)),
  782. off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)), # The offset of the emitter
  783. # emitter is chain/contract-address
  784. emitter.store(Extract(Txn.application_args[1], off.load(), Int(34))),
  785. sequence.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(34), Int(8)))),
  786. # They passed us the correct account? In this case, byte_offset points at the whole block
  787. byte_offset.store(sequence.load() / Int(max_bits)),
  788. MagicAssert(Txn.accounts[1] == get_sig_address(byte_offset.load(), emitter.load())),
  789. # Now, lets go grab the raw byte
  790. byte_offset.store((sequence.load() / Int(8)) % Int(max_bytes)),
  791. b.store(blob.get_byte(Int(1), byte_offset.load())),
  792. # I would hope we've never seen this packet before... throw an exception if we have
  793. MagicAssert(GetBit(b.load(), sequence.load() % Int(8)) == Int(0)),
  794. # Lets mark this bit so that we never see it again
  795. blob.set_byte(Int(1), byte_offset.load(), SetBit(b.load(), sequence.load() % Int(8), Int(1)))
  796. )
  797. def nop():
  798. return Seq([Approve()])
  799. router = Cond(
  800. [METHOD == Bytes("nop"), nop()],
  801. [METHOD == Bytes("receiveAttest"), receiveAttest()],
  802. [METHOD == Bytes("attestToken"), attestToken()],
  803. [METHOD == Bytes("completeTransfer"), completeTransfer()],
  804. [METHOD == Bytes("sendTransfer"), sendTransfer()],
  805. [METHOD == Bytes("optin"), do_optin()],
  806. [METHOD == Bytes("governance"), governance()]
  807. )
  808. on_create = Seq( [
  809. App.globalPut(Bytes("coreid"), Btoi(Txn.application_args[0])),
  810. App.globalPut(Bytes("coreAddr"), Txn.application_args[1]),
  811. App.globalPut(Bytes("validUpdateApproveHash"), Bytes("")),
  812. Return(Int(1))
  813. ])
  814. def getOnUpdate():
  815. return Seq( [
  816. MagicAssert(Sha512_256(Concat(Bytes("Program"), Txn.approval_program())) == App.globalGet(Bytes("validUpdateApproveHash"))),
  817. MagicAssert(And(Len(Txn.clear_state_program()) == Int(4), Extract(Txn.clear_state_program(), Int(1), Int(3)) == Bytes("base16", "810143"))),
  818. Return(Int(1))
  819. ] )
  820. on_update = getOnUpdate()
  821. @Subroutine(TealType.uint64)
  822. def optin():
  823. # Alias for readability
  824. algo_seed = Gtxn[Txn.group_index() - Int(1)]
  825. optin = Txn
  826. well_formed_optin = And(
  827. # Check that we're paying it
  828. algo_seed.type_enum() == TxnType.Payment,
  829. algo_seed.amount() == Int(seed_amt),
  830. algo_seed.receiver() == optin.sender(),
  831. # Check that its an opt in to us
  832. optin.type_enum() == TxnType.ApplicationCall,
  833. optin.on_completion() == OnComplete.OptIn,
  834. optin.application_id() == Global.current_application_id(),
  835. optin.rekey_to() == Global.current_application_address(),
  836. optin.application_args.length() == Int(0)
  837. )
  838. return Seq(
  839. # Make sure its a valid optin
  840. MagicAssert(well_formed_optin),
  841. # Init by writing to the full space available for the sender (Int(0))
  842. blob.zero(Int(0)),
  843. # we gucci
  844. Int(1)
  845. )
  846. on_optin = Seq( [
  847. Return(optin())
  848. ])
  849. return Cond(
  850. [Txn.application_id() == Int(0), on_create],
  851. [Txn.on_completion() == OnComplete.UpdateApplication, on_update],
  852. [Txn.on_completion() == OnComplete.DeleteApplication, on_delete],
  853. [Txn.on_completion() == OnComplete.OptIn, on_optin],
  854. [Txn.on_completion() == OnComplete.NoOp, router]
  855. )
  856. def get_token_bridge(genTeal, approve_name, clear_name, client: AlgodClient, seed_amt: int, tmpl_sig: TmplSig, devMode: bool) -> Tuple[bytes, bytes]:
  857. if not devMode:
  858. client = AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "https://testnet-api.algonode.cloud")
  859. APPROVAL_PROGRAM = fullyCompileContract(genTeal, client, approve_token_bridge(seed_amt, tmpl_sig, devMode), approve_name, devMode)
  860. CLEAR_STATE_PROGRAM = fullyCompileContract(genTeal, client, clear_token_bridge(), clear_name, devMode)
  861. return APPROVAL_PROGRAM, CLEAR_STATE_PROGRAM
  862. def cli(output_approval, output_clear):
  863. seed_amt = 1002000
  864. tmpl_sig = TmplSig("sig")
  865. client = AlgodClient("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "https://testnet-api.algonode.cloud")
  866. approval, clear = get_token_bridge(True, output_approval, output_clear, client, seed_amt, tmpl_sig, False)
  867. if __name__ == "__main__":
  868. cli(sys.argv[1], sys.argv[2])