diff --git a/DEVELOP.md b/DEVELOP.md index 1d7e641f1d..42517941e8 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -182,3 +182,7 @@ Node logs: Account list: kubectl exec -c goal-kmd algorand-0 -- ./goal account list + +Get yourself a working shell: + + kubectl exec -c goal-kmd algorand-0 -it shell-demo -- /bin/bash diff --git a/Dockerfile.teal b/Dockerfile.teal new file mode 100644 index 0000000000..63c9a3f45e --- /dev/null +++ b/Dockerfile.teal @@ -0,0 +1,21 @@ +# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2 +FROM docker.io/fedora:34 AS teal-build +RUN dnf -y install python3-pip + +COPY staging/algorand/teal /teal + +# Install pyTEAL dependencies +COPY third_party/algorand/Pipfile.lock Pipfile.lock +COPY third_party/algorand/Pipfile Pipfile + +RUN pip install pipenv +RUN pipenv install + +# Regenerate TEAL assembly +RUN pipenv run python3 /teal/wormhole/pyteal/vaa-processor.py vaa-processor-approval.teal vaa-processor-clear.teal +RUN pipenv run python3 /teal/wormhole/pyteal/vaa-verify.py 0 vaa-verify.teal + +FROM scratch AS teal-export +COPY --from=teal-build /vaa-processor-approval.teal third_party/algorand/teal/ +COPY --from=teal-build /vaa-processor-clear.teal third_party/algorand/teal/ +COPY --from=teal-build /vaa-verify.teal third_party/algorand/teal/ diff --git a/Tiltfile b/Tiltfile index be91adc5fe..772793ac2d 100644 --- a/Tiltfile +++ b/Tiltfile @@ -86,6 +86,14 @@ local_resource( trigger_mode = trigger_mode, ) +local_resource( + name = "teal-gen", + deps = ["staging/algorand/teal"], + cmd = "tilt docker build -- --target teal-export -f Dockerfile.teal -o type=local,dest=. .", + env = {"DOCKER_BUILDKIT": "1"}, + trigger_mode = trigger_mode, +) + # wasm local_resource( @@ -301,6 +309,7 @@ docker_build( k8s_resource( "algorand", + resource_deps = ["teal-gen"], port_forwards = [ port_forward(4001, name = "Algorand RPC [:4001]", host = webHost), port_forward(4002, name = "Algorand KMD [:4002]", host = webHost), diff --git a/devnet/algorand.yaml b/devnet/algorand.yaml index 8790bee512..56b9939710 100644 --- a/devnet/algorand.yaml +++ b/devnet/algorand.yaml @@ -52,7 +52,7 @@ spec: command: - /bin/sh - -c - - ./goal kmd start -d /network/Node && ./goal account list && sleep infinity + - ./goal kmd start -d /network/Node && ./goal account list && /setup/setup.sh && sleep infinity ports: - containerPort: 4002 name: kmd diff --git a/devnet/node.yaml b/devnet/node.yaml index ff6c9fcb6c..42007eaa10 100644 --- a/devnet/node.yaml +++ b/devnet/node.yaml @@ -94,6 +94,12 @@ spec: - http://terra-lcd:1317 - --terraContract - terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 + - --algorandRPC + - http://localhost:4001 + - --algorandToken + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - --algorandContract + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - --solanaContract - Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o - --solanaWS diff --git a/node/cmd/guardiand/node.go b/node/cmd/guardiand/node.go index 7352178865..da4da661d8 100644 --- a/node/cmd/guardiand/node.go +++ b/node/cmd/guardiand/node.go @@ -37,6 +37,8 @@ import ( "github.com/certusone/wormhole/node/pkg/terra" + "github.com/certusone/wormhole/node/pkg/algorand" + ipfslog "github.com/ipfs/go-log/v2" ) @@ -78,6 +80,10 @@ var ( terraLCD *string terraContract *string + algorandRPC *string + algorandToken *string + algorandContract *string + solanaWsRPC *string solanaRPC *string @@ -145,6 +151,10 @@ func init() { terraLCD = NodeCmd.Flags().String("terraLCD", "", "Path to LCD service root for http calls") terraContract = NodeCmd.Flags().String("terraContract", "", "Wormhole contract address on Terra blockchain") + algorandRPC = NodeCmd.Flags().String("algorandRPC", "", "Algorand RPC URL") + algorandToken = NodeCmd.Flags().String("algorandToken", "", "Algorand access token") + algorandContract = NodeCmd.Flags().String("algorandContract", "", "Algorand contract") + solanaWsRPC = NodeCmd.Flags().String("solanaWS", "", "Solana Websocket URL (required") solanaRPC = NodeCmd.Flags().String("solanaRPC", "", "Solana RPC URL (required") @@ -248,6 +258,9 @@ func runNode(cmd *cobra.Command, args []string) { readiness.RegisterComponent(common.ReadinessEthSyncing) readiness.RegisterComponent(common.ReadinessSolanaSyncing) readiness.RegisterComponent(common.ReadinessTerraSyncing) + if *unsafeDevMode { + readiness.RegisterComponent(common.ReadinessAlgorandSyncing) + } readiness.RegisterComponent(common.ReadinessBSCSyncing) readiness.RegisterComponent(common.ReadinessPolygonSyncing) readiness.RegisterComponent(common.ReadinessAvalancheSyncing) @@ -384,6 +397,18 @@ func runNode(cmd *cobra.Command, args []string) { logger.Fatal("Please specify --terraContract") } + if *unsafeDevMode { + if *algorandRPC == "" { + logger.Fatal("Please specify --algorandRPC") + } + if *algorandToken == "" { + logger.Fatal("Please specify --algorandToken") + } + if *algorandContract == "" { + logger.Fatal("Please specify --algorandContract") + } + } + if *bigTablePersistenceEnabled { if *bigTableGCPProject == "" { logger.Fatal("Please specify --bigTableGCPProject") @@ -585,6 +610,13 @@ func runNode(cmd *cobra.Command, args []string) { return err } + if *unsafeDevMode { + if err := supervisor.Run(ctx, "algorandwatch", + algorand.NewWatcher(*algorandRPC, *algorandToken, *algorandContract, lockC, setC).Run); err != nil { + return err + } + } + if err := supervisor.Run(ctx, "solwatch-confirmed", solana.NewSolanaWatcher(*solanaWsRPC, *solanaRPC, solAddress, lockC, rpc.CommitmentConfirmed).Run); err != nil { return err diff --git a/node/pkg/algorand/watcher.go b/node/pkg/algorand/watcher.go new file mode 100644 index 0000000000..a468cde032 --- /dev/null +++ b/node/pkg/algorand/watcher.go @@ -0,0 +1,30 @@ +package algorand + +import ( + "context" + "github.com/certusone/wormhole/node/pkg/common" + "github.com/certusone/wormhole/node/pkg/readiness" +) + +type ( + // Watcher is responsible for looking over Algorand blockchain and reporting new transactions to the contract + Watcher struct { + urlRPC string + urlToken string + contract string + + msgChan chan *common.MessagePublication + setChan chan *common.GuardianSet + } +) + +// NewWatcher creates a new Algorand contract watcher +func NewWatcher(urlRPC string, urlToken string, contract string, lockEvents chan *common.MessagePublication, setEvents chan *common.GuardianSet) *Watcher { + return &Watcher{urlRPC: urlRPC, urlToken: urlToken, contract: contract, msgChan: lockEvents, setChan: setEvents} +} + +func (e *Watcher) Run(ctx context.Context) error { + readiness.SetReady(common.ReadinessAlgorandSyncing) + + select {} +} diff --git a/node/pkg/common/readiness.go b/node/pkg/common/readiness.go index 095de1c0ec..0716e0c393 100644 --- a/node/pkg/common/readiness.go +++ b/node/pkg/common/readiness.go @@ -6,6 +6,7 @@ const ( ReadinessEthSyncing readiness.Component = "ethSyncing" ReadinessSolanaSyncing readiness.Component = "solanaSyncing" ReadinessTerraSyncing readiness.Component = "terraSyncing" + ReadinessAlgorandSyncing readiness.Component = "algorandSyncing" ReadinessBSCSyncing readiness.Component = "bscSyncing" ReadinessPolygonSyncing readiness.Component = "polygonSyncing" ReadinessEthRopstenSyncing readiness.Component = "ethRopstenSyncing" diff --git a/node/pkg/vaa/structs.go b/node/pkg/vaa/structs.go index f3113bad5c..b6b52bb24c 100644 --- a/node/pkg/vaa/structs.go +++ b/node/pkg/vaa/structs.go @@ -98,6 +98,8 @@ func (c ChainID) String() string { return "avalanche" case ChainIDOasis: return "oasis" + case ChainIDAlgorand: + return "algorand" case ChainIDEthereumRopsten: return "ethereum-ropsten" default: @@ -123,6 +125,8 @@ func ChainIDFromString(s string) (ChainID, error) { return ChainIDAvalanche, nil case "oasis": return ChainIDOasis, nil + case "algorand": + return ChainIDAlgorand, nil case "ethereum-ropsten": return ChainIDEthereumRopsten, nil default: @@ -146,6 +150,8 @@ const ( ChainIDAvalanche ChainID = 6 // ChainIDOasis is the ChainID of Oasis ChainIDOasis ChainID = 7 + // ChainIDAlgorand is the ChainID of Algorand + ChainIDAlgorand ChainID = 8 // ChainIDEthereumRopsten is the ChainID of Ethereum Ropsten ChainIDEthereumRopsten ChainID = 10001 diff --git a/proto/publicrpc/v1/publicrpc.proto b/proto/publicrpc/v1/publicrpc.proto index 6293e6dbd0..8ec10f8d43 100644 --- a/proto/publicrpc/v1/publicrpc.proto +++ b/proto/publicrpc/v1/publicrpc.proto @@ -16,6 +16,7 @@ enum ChainID { CHAIN_ID_POLYGON = 5; CHAIN_ID_AVALANCHE = 6; CHAIN_ID_OASIS = 7; + CHAIN_ID_ALGORAND = 8; // Special case - Eth has two testnets. CHAIN_ID_ETHEREUM is Goerli, // but we also want to connect to Ropsten, so we add a separate chain. CHAIN_ID_ETHEREUM_ROPSTEN = 10001; diff --git a/sdk/js/src/utils/consts.ts b/sdk/js/src/utils/consts.ts index bdd21957e1..25240b1b65 100644 --- a/sdk/js/src/utils/consts.ts +++ b/sdk/js/src/utils/consts.ts @@ -1,4 +1,4 @@ -export type ChainId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10001; +export type ChainId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10001; export const CHAIN_ID_SOLANA: ChainId = 1; export const CHAIN_ID_ETH: ChainId = 2; export const CHAIN_ID_TERRA: ChainId = 3; @@ -6,6 +6,7 @@ export const CHAIN_ID_BSC: ChainId = 4; export const CHAIN_ID_POLYGON: ChainId = 5; export const CHAIN_ID_AVAX: ChainId = 6; export const CHAIN_ID_OASIS: ChainId = 7; +export const CHAIN_ID_ALGORAND: ChainId = 8; export const CHAIN_ID_ETHEREUM_ROPSTEN: ChainId = 10001; export const WSOL_ADDRESS = "So11111111111111111111111111111111111111112"; diff --git a/staging/algorand/scripts/createapp.sh b/staging/algorand/scripts/createapp.sh old mode 100644 new mode 100755 diff --git a/staging/algorand/teal/wormhole/pyteal/vaa-verify.py b/staging/algorand/teal/wormhole/pyteal/vaa-verify.py index e7a9515f59..bb1a5a35b7 100644 --- a/staging/algorand/teal/wormhole/pyteal/vaa-verify.py +++ b/staging/algorand/teal/wormhole/pyteal/vaa-verify.py @@ -120,10 +120,10 @@ def vaa_verify_program(vaa_processor_app_id): print("VAA Verify Stateless Program, (c) 2021-22 Randlabs Inc. ") print("Compiling...") - if len(sys.argv) >= 2: + if len(sys.argv) >= 1: appid = sys.argv[1] - if len(sys.argv) >= 3: + if len(sys.argv) >= 2: outfile = sys.argv[2] with open(outfile, "w") as f: diff --git a/third_party/algorand/.gitignore b/third_party/algorand/.gitignore new file mode 100644 index 0000000000..1019a8d097 --- /dev/null +++ b/third_party/algorand/.gitignore @@ -0,0 +1,2 @@ +# Generated +teal diff --git a/third_party/algorand/Dockerfile b/third_party/algorand/Dockerfile index a6848f6b42..3cf8024868 100644 --- a/third_party/algorand/Dockerfile +++ b/third_party/algorand/Dockerfile @@ -3,6 +3,11 @@ FROM docker.io/algorand/stable:3.2.1@sha256:0a87978492680fd98e2cc410f59f2bfd7fef RUN mkdir -p /setup ADD template.json /setup/ +ADD setup.sh /setup/ +ADD setup.py /setup/ +ADD teal/vaa-verify.teal /setup/ +ADD teal/vaa-processor-clear.teal /setup/ +ADD teal/vaa-processor-approval.teal /setup/ RUN ./goal network create -n sandnet -r /network -t /setup/template.json && echo rawr @@ -14,3 +19,9 @@ ADD config.json /network/Node/config.json ADD kmd_config.json /network/Node/kmd-v0.5/kmd_config.json ENV ALGORAND_DATA=/network/Node + +ADD Pipfile.lock /setup/ +ADD Pipfile /setup/ +RUN apt-get update +RUN apt-get install -y python3-pip +RUN pip install pipenv diff --git a/third_party/algorand/Pipfile b/third_party/algorand/Pipfile new file mode 100644 index 0000000000..9fcd222c58 --- /dev/null +++ b/third_party/algorand/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +py-algorand-sdk = "*" +pyteal = "*" +mypy = "*" +pytest = "*" + +[dev-packages] diff --git a/third_party/algorand/Pipfile.lock b/third_party/algorand/Pipfile.lock new file mode 100644 index 0000000000..a2a16f4862 --- /dev/null +++ b/third_party/algorand/Pipfile.lock @@ -0,0 +1,316 @@ +{ + "_meta": { + "hash": { + "sha256": "7d3b87829ef46d00b82e4450cc45b6ca299dc0a5e9bba990744eda1c901d8af4" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "version": "==1.15.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "msgpack": { + "hashes": [ + "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc", + "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147", + "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3", + "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba", + "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39", + "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85", + "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9", + "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a", + "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec", + "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88", + "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e", + "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a", + "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b", + "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1", + "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3", + "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef", + "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079", + "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52", + "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a", + "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a", + "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4", + "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996", + "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73", + "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a", + "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920", + "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7", + "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d", + "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770", + "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50", + "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2", + "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2", + "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d", + "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea", + "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611" + ], + "version": "==1.0.3" + }, + "mypy": { + "hashes": [ + "sha256:13b3c110309b53f5a62aa1b360f598124be33a42563b790a2a9efaacac99f1fc", + "sha256:140174e872d20d4768124a089b9f9fc83abd6a349b7f8cc6276bc344eb598922", + "sha256:31895b0b3060baf15bf76e789d94722c026f673b34b774bba9e8772295edccff", + "sha256:331a81d2c9bf1be25317260a073b41f4584cd11701a7c14facef0aa5a005e843", + "sha256:40cb062f1b7ff4cd6e897a89d8ddc48c6ad7f326b5277c93a8c559564cc1551c", + "sha256:41f3575b20714171c832d8f6c7aaaa0d499c9a2d1b8adaaf837b4c9065c38540", + "sha256:431be889ffc8d9681813a45575c42e341c19467cbfa6dd09bf41467631feb530", + "sha256:562a0e335222d5bbf5162b554c3afe3745b495d67c7fe6f8b0d1b5bace0c1eeb", + "sha256:618e677aabd21f30670bffb39a885a967337f5b112c6fb7c79375e6dced605d6", + "sha256:69b5a835b12fdbfeed84ef31152d41343d32ccb2b345256d8682324409164330", + "sha256:71c77bd885d2ce44900731d4652d0d1c174dc66a0f11200e0c680bdedf1a6b37", + "sha256:82e6c15675264e923b60a11d6eb8f90665504352e68edfbb4a79aac7a04caddd", + "sha256:98b4f91a75fed2e4c6339e9047aba95968d3a7c4b91e92ab9dc62c0c583564f4", + "sha256:993c2e52ea9570e6e872296c046c946377b9f5e89eeb7afea2a1524cf6e50b27", + "sha256:9cd316e9705555ca6a50670ba5fb0084d756d1d8cb1697c83820b1456b0bc5f3", + "sha256:a55438627f5f546192f13255a994d6d1cf2659df48adcf966132b4379fd9c86b", + "sha256:df0fec878ccfcb2d1d2306ba31aa757848f681e7bbed443318d9bbd4b0d0fe9a", + "sha256:e091fe58b4475b3504dc7c3022ff7f4af2f9e9ddf7182047111759ed0973bbde", + "sha256:f8b2059f73878e92eff7ed11a03515d6572f4338a882dd7547b5f7dd242118e6", + "sha256:ffb1e57ec49a30e3c0ebcfdc910ae4aceb7afb649310b7355509df6b15bd75f6" + ], + "index": "pypi", + "version": "==0.920" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "py-algorand-sdk": { + "hashes": [ + "sha256:110480dc5baf2102721a5fe860c22fb445a737dfbb0a2e82ed97fee03b792d19", + "sha256:691771118f88855affbb57853a73113ce0ea554d7b9f1bbe50e309a83a632158" + ], + "index": "pypi", + "version": "==1.8.0" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pycryptodomex": { + "hashes": [ + "sha256:08c809e9f4be8d4f9948cf4d5ebc7431bbd9e1c0cd5ff478d0d5229f1bc4ad50", + "sha256:097095a7c24b9e7eec865417f620f78adf218162c03b68e4fde194bf87801a67", + "sha256:0981e8071d2ee457d842821438f833e362eed9a25a445d54ad7610b24293118f", + "sha256:1bd9d158afa33dca04748b23e7b9d4055f8c8015ace2e972a866519af02d5eed", + "sha256:1f6c370abf11546b1c9b70062918d601ac8fb7ff113554601b43175eed7480ef", + "sha256:2595b7be43b8b2da953ea3506a8d71c07fc9b479d5c118b0c44a5eca2a1664f6", + "sha256:2d173a5db4e306cd32558b1a3ceb45bd2ebeb6596525fd5945963798b3851e3d", + "sha256:33c06d6819a0204fac675d100f92aa472c704cd774a47171a5949c75c1040ed6", + "sha256:3559da56e1045ad567e69fcc74245073fe1943b07b137bfd1073c7a540a89df7", + "sha256:3bfa2936f8391bfaa17ed6a5c726e33acad56d7b47b8bf824b1908b16b140025", + "sha256:4361881388817f89aa819a553e987200a6eb664df995632b063997dd373a7cee", + "sha256:43af464dcac1ae53e6e14a0ae6f08373b538f3c49fb9e426423618571edfecff", + "sha256:44097663c62b3aa03b5b403b816dedafa592984e8c6857a061ade41f32a2666e", + "sha256:4cbaea8ab8bfa283e6219af39624d921f72f8174765a35416aab4d4b4dec370e", + "sha256:5b0fd9fc81d43cd54dc8e4b2df8730ffd1e34f1f0679920deae16f6487aa1414", + "sha256:676d9f4286f490612fa35ca8fe4b1fced8ff18e653abc1dda34fbf166129d6c2", + "sha256:79ad48096ceb5c714fbc4dc82e3e6b37f095f627b1fef90d94d85e19a19d6611", + "sha256:83379f1fd7b99c4993f5e905f2a6ddb9003996655151ea3c2ee77332ad009d08", + "sha256:88dc997e3e9199a0d67b547fba36c6d1c54fca7d83c4bfe0d3f34f55a4717a2c", + "sha256:8c5b97953130ff76500c6e8e159f2b881c737ebf00034006517b57f382d5317c", + "sha256:922e9dac0166e4617e5c7980d2cff6912a6eb5cb5c13e7ece222438650bd7f66", + "sha256:9c037aaf6affc8f7c4f6f9f6279aa57dd526734246fb5221a0fff3124f57e0b1", + "sha256:a896b41c518269c1cceb582e298a868e6c74bb3cbfd362865ea686c78aebe91d", + "sha256:b1a6f17c4ad896ed628663b021cd797b098c7e9537fd259958f6ffb3b8921081", + "sha256:b5ddaee74e1f06af9c0765a147904dddacf4ca9707f8f079e14e2b14b4f5a544", + "sha256:d55374ebc36de7a3217f2e2318886f0801dd5e486e21aba1fc4ca08e3b6637d7", + "sha256:ddac6a092b97aa11d2a21aec33e941b4453ef774da3d98f2b7c1e01da05e6d5e", + "sha256:de9832ac3c51484fe1757ca4303695423b16cfa54874dae9239bf41f50a2affa", + "sha256:e42a82c63952ed70be3c13782c6984a519b514e6b10108a9647c7576b6c86650", + "sha256:ea8e83bf4731a2369350d7771a1f2ef8d72ad3da70a37d86b1374be8c675abd0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.12.0" + }, + "pynacl": { + "hashes": [ + "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", + "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", + "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", + "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", + "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", + "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", + "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", + "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", + "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", + "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", + "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", + "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", + "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", + "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", + "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", + "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", + "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", + "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.0" + }, + "pyparsing": { + "hashes": [ + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.6" + }, + "pyteal": { + "hashes": [ + "sha256:6a7fbb2155ee79e0b6dfbd635e6ac4508a0c2279ddb4203f1c356b47bd684bb6", + "sha256:e395cf30fce630ac26157132ae4e0240845060a858a1a162aeac8b92a286b554" + ], + "index": "pypi", + "version": "==0.9.1" + }, + "pytest": { + "hashes": [ + "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", + "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + ], + "index": "pypi", + "version": "==6.2.5" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224", + "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + } + }, + "develop": {} +} diff --git a/third_party/algorand/setup.py b/third_party/algorand/setup.py new file mode 100644 index 0000000000..bc9a41faf7 --- /dev/null +++ b/third_party/algorand/setup.py @@ -0,0 +1,248 @@ +from time import time, sleep +from typing import List, Tuple, Dict, Any, Optional, Union +from base64 import b64decode +import base64 +import random +import hashlib + +from algosdk.v2client.algod import AlgodClient +from algosdk.kmd import KMDClient +from algosdk import account, mnemonic +from algosdk.future import transaction +from algosdk.encoding import decode_address +from pyteal import compileTeal, Mode, Expr +from pyteal import * +from algosdk.logic import get_application_address + +import pprint + +# Q5XDfcbiqiBwfMlY3gO1Mb0vyNCO+szD3v9azhrG16iO5Z5aTduNzeut/FLG0NOG0+txrBGN6lhi5iwytgkyKg== + +# position atom discover cluster fiction amused toe siren slam author surround spread garage craft isolate whisper kangaroo kitchen lend toss culture various effort absent kidney + +class Account: + """Represents a private key and address for an Algorand account""" + + def __init__(self, privateKey: str) -> None: + self.sk = privateKey + self.addr = account.address_from_private_key(privateKey) +# print (privateKey + " -> " + self.getMnemonic()) + + def getAddress(self) -> str: + return self.addr + + def getPrivateKey(self) -> str: + return self.sk + + def getMnemonic(self) -> str: + return mnemonic.from_private_key(self.sk) + + @classmethod + def FromMnemonic(cls, m: str) -> "Account": + return cls(mnemonic.to_private_key(m)) + +class PendingTxnResponse: + def __init__(self, response: Dict[str, Any]) -> None: + self.poolError: str = response["pool-error"] + self.txn: Dict[str, Any] = response["txn"] + + self.applicationIndex: Optional[int] = response.get("application-index") + self.assetIndex: Optional[int] = response.get("asset-index") + self.closeRewards: Optional[int] = response.get("close-rewards") + self.closingAmount: Optional[int] = response.get("closing-amount") + self.confirmedRound: Optional[int] = response.get("confirmed-round") + self.globalStateDelta: Optional[Any] = response.get("global-state-delta") + self.localStateDelta: Optional[Any] = response.get("local-state-delta") + self.receiverRewards: Optional[int] = response.get("receiver-rewards") + self.senderRewards: Optional[int] = response.get("sender-rewards") + + self.innerTxns: List[Any] = response.get("inner-txns", []) + self.logs: List[bytes] = [b64decode(l) for l in response.get("logs", [])] + +class Setup: + def __init__(self) -> None: + self.ALGOD_ADDRESS = "http://localhost:4001" + self.ALGOD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + self.FUNDING_AMOUNT = 100_000_000 + + self.KMD_ADDRESS = "http://localhost:4002" + self.KMD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + self.KMD_WALLET_NAME = "unencrypted-default-wallet" + self.KMD_WALLET_PASSWORD = "" + + self.TARGET_ACCOUNT = "position atom discover cluster fiction amused toe siren slam author surround spread garage craft isolate whisper kangaroo kitchen lend toss culture various effort absent kidney" + + self.kmdAccounts : Optional[List[Account]] = None + + self.accountList : List[Account] = [] + + self.APPROVAL_PROGRAM = b"" + self.CLEAR_STATE_PROGRAM = b"" + + def waitForTransaction( + self, client: AlgodClient, txID: str, timeout: int = 10 + ) -> PendingTxnResponse: + lastStatus = client.status() + lastRound = lastStatus["last-round"] + startRound = lastRound + + while lastRound < startRound + timeout: + pending_txn = client.pending_transaction_info(txID) + + if pending_txn.get("confirmed-round", 0) > 0: + return PendingTxnResponse(pending_txn) + + if pending_txn["pool-error"]: + raise Exception("Pool error: {}".format(pending_txn["pool-error"])) + + lastStatus = client.status_after_block(lastRound + 1) + + lastRound += 1 + + raise Exception( + "Transaction {} not confirmed after {} rounds".format(txID, timeout) + ) + + def getKmdClient(self) -> KMDClient: + return KMDClient(self.KMD_TOKEN, self.KMD_ADDRESS) + + def getGenesisAccounts(self) -> List[Account]: + if self.kmdAccounts is None: + kmd = self.getKmdClient() + + wallets = kmd.list_wallets() + walletID = None + for wallet in wallets: + if wallet["name"] == self.KMD_WALLET_NAME: + walletID = wallet["id"] + break + + if walletID is None: + raise Exception("Wallet not found: {}".format(self.KMD_WALLET_NAME)) + + walletHandle = kmd.init_wallet_handle(walletID, self.KMD_WALLET_PASSWORD) + + try: + addresses = kmd.list_keys(walletHandle) + privateKeys = [ + kmd.export_key(walletHandle, self.KMD_WALLET_PASSWORD, addr) + for addr in addresses + ] + self.kmdAccounts = [Account(sk) for sk in privateKeys] + finally: + kmd.release_wallet_handle(walletHandle) + + return self.kmdAccounts + + def getTargetAccount(self) -> Account: + return Account.FromMnemonic(self.TARGET_ACCOUNT) + + def fundTargetAccount(self, client: AlgodClient, target: Account): + print("fundTargetAccount") + genesisAccounts = self.getGenesisAccounts() + suggestedParams = client.suggested_params() + + for fundingAccount in genesisAccounts: + txn = transaction.PaymentTxn( + sender=fundingAccount.getAddress(), + receiver=target.getAddress(), + amt=self.FUNDING_AMOUNT, + sp=suggestedParams, + ) + pprint.pprint(txn) + print("signing txn") + stxn = txn.sign(fundingAccount.getPrivateKey()) + print("sending txn") + client.send_transaction(stxn) + print("waiting for txn") + self.waitForTransaction(client, stxn.get_txid()) + + def getAlgodClient(self) -> AlgodClient: + return AlgodClient(self.ALGOD_TOKEN, self.ALGOD_ADDRESS) + + def getBalances(self, client: AlgodClient, account: str) -> Dict[int, int]: + balances: Dict[int, int] = dict() + + accountInfo = client.account_info(account) + + # set key 0 to Algo balance + balances[0] = accountInfo["amount"] + + assets: List[Dict[str, Any]] = accountInfo.get("assets", []) + for assetHolding in assets: + assetID = assetHolding["asset-id"] + amount = assetHolding["amount"] + balances[assetID] = amount + + return balances + + def setup(self): + self.client = self.getAlgodClient() + + self.target = self.getTargetAccount() + + b = self.getBalances(self.client, self.target.getAddress()) + if (b[0] < 100000000): + print("Account needs money... funding it") + self.fundTargetAccount(self.client, self.target) + print(self.getBalances(self.client, self.target.getAddress())) + + + def deploy(self): + vaa_processor_approval = self.client.compile(open("vaa-processor-approval.teal", "r").read()) + vaa_processor_clear = self.client.compile(open("vaa-processor-clear.teal", "r").read()) + vaa_verify = self.client.compile(open("vaa-verify.teal", "r").read()) + verify_hash = vaa_verify['hash'] + print("verify_hash " + verify_hash + " " + str(len(decode_address(verify_hash)))) + + globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=20) + localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=0) + + app_args = [ "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe", 0, 0 ] + + txn = transaction.ApplicationCreateTxn( + sender=self.target.getAddress(), + on_complete=transaction.OnComplete.NoOpOC, + approval_program=b64decode(vaa_processor_approval["result"]), + clear_program=b64decode(vaa_processor_clear["result"]), + global_schema=globalSchema, + local_schema=localSchema, + app_args=app_args, + sp=self.client.suggested_params(), + ) + + signedTxn = txn.sign(self.target.getPrivateKey()) + self.client.send_transaction(signedTxn) + response = self.waitForTransaction(self.client, signedTxn.get_txid()) + assert response.applicationIndex is not None and response.applicationIndex > 0 + print("app_id: ", response.applicationIndex) + + appAddr = get_application_address(response.applicationIndex) + suggestedParams = self.client.suggested_params() + appCallTxn = transaction.ApplicationCallTxn( + sender=self.target.getAddress(), + index=response.applicationIndex, + on_complete=transaction.OnComplete.NoOpOC, + app_args=[b"setvphash", decode_address(verify_hash)], + sp=suggestedParams, + ) + + signedAppCallTxn = appCallTxn.sign(self.target.getPrivateKey()) + self.client.send_transactions([signedAppCallTxn]) + response = self.waitForTransaction(self.client, appCallTxn.get_txid()) + print("set the vp hash to the stateless contract") + + appCallTxn = transaction.PaymentTxn( + sender=self.target.getAddress(), + receiver=verify_hash, + amt=500000, + sp=suggestedParams, + ) + signedAppCallTxn = appCallTxn.sign(self.target.getPrivateKey()) + self.client.send_transactions([signedAppCallTxn]) + response = self.waitForTransaction(self.client, appCallTxn.get_txid()) + print("funded the stateless contract") + +s = Setup() +s.setup() +s.deploy() diff --git a/third_party/algorand/setup.sh b/third_party/algorand/setup.sh new file mode 100755 index 0000000000..d854a21229 --- /dev/null +++ b/third_party/algorand/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +dn="$(dirname "$0")" +cd $dn + +pipenv install +pipenv run python3 setup.py