1) Add payload3 support into typescript

2) Add tests for payload3
3) Change the contract API for payload3 so that the destination is the appId instead of the app address
   and no longer part of the data
4) add a testApp into devnet
This commit is contained in:
Josh Siegel 2022-05-06 18:37:31 +00:00 committed by jumpsiegel
parent 34ea483dbe
commit 4e53392819
10 changed files with 219 additions and 25 deletions

View File

@ -1,3 +1,7 @@
npm run test -- algorand
https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2statuswait-for-block-afterround
current algorand machine size:
https://howbigisalgorand.com/
@ -281,3 +285,29 @@ index 9e224c2..f1714ea 100755
--server ":$PORT" \
-P "$CONNECTION_STRING" \
--algod-net "${ALGOD_ADDR}" \
--
#!/usr/bin/env bash
if [ ! -d _sandbox ]; then
echo We need to create it...
git clone https://github.com/algorand/sandbox.git _sandbox
fi
if [ "`grep enable-all-parameters _sandbox/images/indexer/start.sh | wc -l`" == "0" ]; then
echo the indexer is incorrectly configured
sed -i -e 's/dev-mode/dev-mode --enable-all-parameters/' _sandbox/images/indexer/start.sh
echo delete all the existing docker images
./sandbox clean
fi
./sandbox up dev
echo "run the tests"
cd test
python3 test.py
echo "bring the sandbox down"
cd ..
./sandbox down

View File

@ -35,6 +35,8 @@ from algosdk.future.transaction import LogicSig
from token_bridge import get_token_bridge
from test_contract import get_test_app
from algosdk.v2client import indexer
import pprint
@ -469,6 +471,40 @@ class PortalCore:
return response.applicationIndex
def createTestApp(
self,
client: AlgodClient,
sender: Account,
) -> int:
approval, clear = get_test_app(client)
globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=30)
localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
txn = transaction.ApplicationCreateTxn(
sender=sender.getAddress(),
on_complete=transaction.OnComplete.NoOpOC,
approval_program=b64decode(approval["result"]),
clear_program=b64decode(clear["result"]),
global_schema=globalSchema,
local_schema=localSchema,
sp=client.suggested_params(),
)
signedTxn = txn.sign(sender.getPrivateKey())
client.send_transaction(signedTxn)
response = self.waitForTransaction(client, signedTxn.get_txid())
assert response.applicationIndex is not None and response.applicationIndex > 0
# Lets give it a bit of money so that it is not a "ghost" account
txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
signedTxn = txn.sign(sender.getPrivateKey())
client.send_transaction(signedTxn)
return response.applicationIndex
def account_exists(self, client, app_id, addr):
try:
ai = client.account_info(addr)
@ -933,7 +969,13 @@ class PortalCore:
# The receiver needs to be optin in to receive the coins... Yeah, the relayer pays for this
addr = encode_address(bytes.fromhex(p["ToAddress"]))
aid = 0
if p["ToChain"] == 8 and p["Type"] == 3:
aid = int.from_bytes(bytes.fromhex(p["ToAddress"]), "big")
addr = get_application_address(aid)
else:
addr = encode_address(bytes.fromhex(p["ToAddress"]))
if a != 0:
foreign_assets.append(a)
@ -954,8 +996,8 @@ class PortalCore:
sp=sp
))
if "appid" in p:
txns[-1].foreign_apps = [p["appid"]]
if aid != 0:
txns[-1].foreign_apps = [aid]
# We need to cover the inner transactions
if p["Fee"] != self.zeroPadBytes:
@ -966,7 +1008,7 @@ class PortalCore:
if p["Meta"] == "TokenBridge Transfer With Payload":
txns.append(transaction.ApplicationCallTxn(
sender=sender.getAddress(),
index=p["appid"],
index=int.from_bytes(bytes.fromhex(p["ToAddress"]), "big"),
on_complete=transaction.OnComplete.NoOpOC,
app_args=[b"completeTransfer", vaa],
foreign_assets = foreign_assets,
@ -1100,8 +1142,7 @@ class PortalCore:
ret["Fee"] = vaa[off:(off + 32)].hex()
off += 32
ret["Payload"] = vaa[off:].hex()
ret["appid"] = int.from_bytes(vaa[off:(off + 8)], "big")
ret["body"] = vaa[off + 8:].hex()
ret["body"] = ret["Payload"]
return ret
@ -1117,7 +1158,12 @@ class PortalCore:
pprint.pprint({"token bridge": str(self.tokenid), "address": get_application_address(self.tokenid), "emitterAddress": decode_address(get_application_address(self.tokenid)).hex()})
if self.devnet or self.args.testnet:
if self.devnet:
print("Create test app")
self.testid = self.createTestApp(self.client, self.foundation)
pprint.pprint({"testapp": str(self.testid)})
suggestedParams = self.client.suggested_params()
fundingAccount = self.getGenesisAccounts()[0]

View File

@ -1 +0,0 @@
../teal

2
algorand/test/teal/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.teal

View File

@ -408,7 +408,7 @@ class AlgoTest(PortalCore):
))
accounts=[emitter_addr, creator, c["address"]]
args = [b"sendTransfer", asset_id, quantity, decode_address(receiver), chain, fee]
args = [b"sendTransfer", asset_id, quantity, receiver, chain, fee]
if None != payload:
args.append(payload)
@ -484,6 +484,11 @@ class AlgoTest(PortalCore):
print("woah! optin succeeded")
def simple_test(self):
vaa = self.parseVAA(bytes.fromhex("01000000000100ddc6993585b909c3e861830244122e0daf45101663942484aa56ee2b51fa3ff016411102f993935d428a5aa0c3ace74facd60822435893b74b24fadde0fbad49006277c3fe0000000000088edf5b0e108c3a1a0a4b704cc89591f2ad8d50df24e991567e640ed720a94be200000000000000060003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800080000000000000000000000000000000000000000000000000000000000000000ff"))
pprint.pprint(vaa)
sys.exit(0)
# q = bytes.fromhex(gt.genAssetMeta(gt.guardianPrivKeys, 1, 1, 1, bytes.fromhex("4523c3F29447d1f32AEa95BEBD00383c4640F1b4"), 1, 8, b"USDC", b"CircleCoin"))
# pprint.pprint(self.parseVAA(q))
# sys.exit(0)
@ -652,7 +657,7 @@ class AlgoTest(PortalCore):
print("Lets transfer that asset to one of our other accounts... first lets create the vaa")
# paul - transferFromAlgorand
sid = self.transferFromAlgorand(client, player2, self.testasset, 100, player3.getAddress(), 8, 0)
sid = self.transferFromAlgorand(client, player2, self.testasset, 100, decode_address(player3.getAddress()), 8, 0)
print("... track down the generated VAA")
vaa = self.getVAA(client, player, sid, self.tokenid)
print(".. and lets pass that to player3")
@ -666,7 +671,7 @@ class AlgoTest(PortalCore):
# Lets split it into two parts... the payload and the fee
print("Lets split it into two parts... the payload and the fee (400 should go to player, 600 should go to player3)")
sid = self.transferFromAlgorand(client, player2, self.testasset, 1000, player3.getAddress(), 8, 400)
sid = self.transferFromAlgorand(client, player2, self.testasset, 1000, decode_address(player3.getAddress()), 8, 400)
print("... track down the generated VAA")
vaa = self.getVAA(client, player, sid, self.tokenid)
# pprint.pprint(self.parseVAA(bytes.fromhex(vaa)))
@ -688,7 +693,7 @@ class AlgoTest(PortalCore):
# paul - transferFromAlgorand
print("Lets transfer algo this time.... first lets create the vaa")
sid = self.transferFromAlgorand(client, player2, 0, 1000000, emptyAccount.getAddress(), 8, 0)
sid = self.transferFromAlgorand(client, player2, 0, 1000000, decode_address(emptyAccount.getAddress()), 8, 0)
print("... track down the generated VAA")
vaa = self.getVAA(client, player, sid, self.tokenid)
# pprint.pprint(vaa)
@ -709,7 +714,7 @@ class AlgoTest(PortalCore):
pprint.pprint(self.getBalances(client, player3.getAddress()))
print("Lets transfer more algo.. split 40/60 with the relayer.. going to player3")
sid = self.transferFromAlgorand(client, player2, 0, 1000000, player3.getAddress(), 8, 400000)
sid = self.transferFromAlgorand(client, player2, 0, 1000000, decode_address(player3.getAddress()), 8, 400000)
print("... track down the generated VAA")
vaa = self.getVAA(client, player, sid, self.tokenid)
print(".. and lets pass that to player3.. but use the previously empty account to relay it")
@ -724,18 +729,23 @@ class AlgoTest(PortalCore):
print("How much is in the player3 account now?")
pprint.pprint(self.getBalances(client, player3.getAddress()))
print("How about a payload3")
sid = self.transferFromAlgorand(client, player2, 0, 100, get_application_address(self.testid), 8, 0, self.testid.to_bytes(8, "big")+b'hi mom')
print("How about a payload3: " + self.testid.to_bytes(32, "big").hex())
sid = self.transferFromAlgorand(client, player2, 0, 100, self.testid.to_bytes(32, "big"), 8, 0, b'hi mom')
print("... track down the generated VAA")
vaa = self.getVAA(client, player, sid, self.tokenid)
pprint.pprint(self.parseVAA(bytes.fromhex(vaa)))
print("testid balance before = ", self.getBalances(client, get_application_address(self.testid)))
print(".. Lets let player3 relay it for us")
self.submitVAA(bytes.fromhex(vaa), client, player3, self.tokenid)
print("testid balance after = ", self.getBalances(client, get_application_address(self.testid)))
# sys.exit(0)
print(".. Ok, now it is time to up the message fees")
bal = self.getBalances(client, get_application_address(self.coreid))

View File

@ -84,8 +84,8 @@ def approve_token_bridge(seed_amt: int, tmpl_sig: TmplSig, devMode: bool):
return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())
@Subroutine(TealType.bytes)
def getNextAddress() -> Expr:
maybe = AppParam.address(Gtxn[Txn.group_index() + Int(1)].application_id())
def getAppAddress(appid : Expr) -> Expr:
maybe = AppParam.address(appid)
return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())
def assert_common_checks(e) -> Expr:
@ -456,6 +456,7 @@ def approve_token_bridge(seed_amt: int, tmpl_sig: TmplSig, devMode: bool):
d = ScratchVar()
zb = ScratchVar()
action = ScratchVar()
aid = ScratchVar()
return Seq([
checkForDuplicate(),
@ -517,13 +518,15 @@ def approve_token_bridge(seed_amt: int, tmpl_sig: TmplSig, devMode: bool):
MagicAssert(Fee.load() <= Amount.load()),
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] == Txn.application_args[0],
Gtxn[tidx.load()].application_args[1] == Txn.application_args[1]
Gtxn[tidx.load()].application_args[1] == Txn.application_args[1],
Gtxn[tidx.load()].application_id() == aid.load()
)),
MagicAssert(getNextAddress() == Destination.load())
Destination.store(getAppAddress(aid.load()))
])),
If(OriginChain.load() == Int(8),

View File

@ -375,8 +375,6 @@ export function _parseVAAAlgorand(vaa: Uint8Array): Map<string, any> {
ret.set("Fee", extract3(vaa, off, 32));
off += 32;
ret.set("Payload", vaa.slice(off));
ret.set("appid", buf.readIntBE(off, 8));
ret.set("body", uint8ArrayToHex(vaa.slice(off + 8)));
}
return ret;
@ -815,7 +813,15 @@ export async function _submitVAAAlgorand(
// The receiver needs to be optin in to receive the coins... Yeah, the relayer pays for this
const addr = encodeAddress(hexToUint8Array(parsedVAA.get("ToAddress")));
let aid = 0;
let addr;
if ((parsedVAA.get("ToChain") == 8) && (parsedVAA.get("Type") == 3)) {
aid = Number(hexToNativeAssetBigIntAlgorand(parsedVAA.get("ToAddress")));
addr = getApplicationAddress(aid);
} else {
addr = encodeAddress(hexToUint8Array(parsedVAA.get("ToAddress")));
}
if (a !== 0) {
foreignAssets.push(a);
@ -863,12 +869,12 @@ export async function _submitVAAAlgorand(
else txs[txs.length - 1].tx.fee = txs[txs.length - 1].tx.fee * 3;
if (meta === "TokenBridge Transfer With Payload") {
txs[txs.length - 1].tx.appForeignApps = [parsedVAA.get("appid")];
txs[txs.length - 1].tx.appForeignApps = [aid];
txs.push({
tx: makeApplicationCallTxnFromObject({
appArgs: [textToUint8Array("completeTransfer"), vaa],
appIndex: parsedVAA.get("appid"),
appIndex: aid,
foreignAssets: foreignAssets,
from: senderAddr,
onComplete: OnApplicationComplete.NoOpOC,

View File

@ -27,7 +27,7 @@ import algosdk, {
waitForConfirmation,
} from "algosdk";
import axios from "axios";
import { ethers } from "ethers";
import { BigNumber, utils, ethers } from "ethers";
import {
approveEth,
attestFromAlgorand,
@ -3085,5 +3085,98 @@ describe("Integration Tests", () => {
done();
})();
});
test("testing algorand payload3", (done) => {
(async () => {
try {
console.log("Starting new test of transferring ETH to algorand.");
const tbAddr: string = getApplicationAddress(TOKEN_BRIDGE_ID);
const decTbAddr: Uint8Array = decodeAddress(tbAddr).publicKey;
const aa: string = uint8ArrayToHex(decTbAddr);
const client: algosdk.Algodv2 = getAlgoClient();
const tempAccts: Account[] = await getTempAccounts();
const numAccts: number = tempAccts.length;
expect(numAccts).toBeGreaterThan(0);
const algoWallet: Account = tempAccts[0];
const Fee: number = 0;
var testapp: number = 8;
var dest = utils.hexZeroPad(BigNumber.from(testapp).toHexString(), 32).substring(2);
console.log("Dest address: ", dest);
const transferTxs = await transferFromAlgorand(
client,
TOKEN_BRIDGE_ID,
CORE_ID,
algoWallet.addr,
BigInt(0),
BigInt(100),
dest,
CHAIN_ID_ALGORAND,
BigInt(Fee),
hexToUint8Array("ff")
);
const transferResult = await signSendAndConfirmAlgorand(
client,
transferTxs,
algoWallet
);
const txSid = parseSequenceFromLogAlgorand(transferResult);
console.log("Getting signed VAA...");
const signedVaa = await getSignedVAAWithRetry(
WORMHOLE_RPC_HOSTS,
CHAIN_ID_ALGORAND,
aa,
txSid,
{ transport: NodeHttpTransport() }
);
console.log("payload3 vaa", uint8ArrayToHex(signedVaa.vaaBytes));
console.log("About to send back into algorand...");
const txns = await redeemOnAlgorand(
client,
TOKEN_BRIDGE_ID,
CORE_ID,
signedVaa.vaaBytes,
algoWallet.addr
);
console.log("signSendAndConfirm...");
const wbefore = await getBalance(client, getApplicationAddress(testapp), BigInt(0));
console.log(
"test app wallet before:",
wbefore
);
await signSendAndConfirmAlgorand(
client,
txns,
algoWallet
);
expect(
await getIsTransferCompletedAlgorand(
client,
TOKEN_BRIDGE_ID,
signedVaa.vaaBytes
)
).toBe(true);
const wafter = await getBalance(client, getApplicationAddress(testapp), BigInt(0));
console.log(
"test app wallet after:",
wafter
);
expect(BigInt(wafter - wbefore) == BigInt(100));
console.log("payload3 sent...");
} catch (e) {
console.error("new test error:", e);
done("new test error");
return;
}
done();
})();
});
});
});

View File

@ -393,6 +393,7 @@ export async function transferFromSolana(
* @param receiver Receiving account
* @param chain Reeiving chain
* @param fee Transfer fee
* @param payload payload for payload3 transfers
* @returns Sequence number of confirmation
*/
export async function transferFromAlgorand(
@ -404,7 +405,8 @@ export async function transferFromAlgorand(
qty: bigint,
receiver: string,
chain: ChainId | ChainName,
fee: bigint
fee: bigint,
payload : Uint8Array | null = null
): Promise<TransactionSignerPair[]> {
const recipientChainId = coalesceChainId(chain);
const tokenAddr: string = getApplicationAddress(tokenBridgeId);
@ -524,6 +526,9 @@ export async function transferFromAlgorand(
bigIntToBytes(recipientChainId, 8),
bigIntToBytes(fee, 8),
];
if (payload != null) {
args.push(payload);
}
let acTxn = makeApplicationCallTxnFromObject({
from: senderAddr,
appIndex: safeBigIntToNumber(tokenBridgeId),