Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .github/workflows/agoric-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,6 @@ jobs:

- name: Make offer for creating EVM Smart Wallet
run: ./integration/scripts/make-account.mjs
env:
makeAccount: true

- name: Relay data from EVM chain
run: |
npm run relay:cosmos &
sleep 150

- name: Get EVM Smart wallet address
run: ./integration/scripts/make-account.mjs
run: npm run relay:cosmos
110 changes: 18 additions & 92 deletions integration/scripts/make-account.mjs
Original file line number Diff line number Diff line change
@@ -1,105 +1,31 @@
#!/usr/bin/env node
// @ts-check
import "./lockdown.mjs";
import {
fetchFromVStorage,
poll,
prepareOffer,
processWalletOffer,
validateEvmAddress,
} from "./utils.mjs";
import { prepareOffer, processWalletOffer } from "./utils.mjs";

const OFFER_FILE = "offer.json";
const CONTAINER_PATH = `/usr/src/${OFFER_FILE}`;
const FROM_ADDRESS = "agoric1rwwley550k9mmk6uq6mm6z4udrg8kyuyvfszjk";
const vStorageUrl = `http://localhost/agoric-lcd/agoric/vstorage/data/published.wallet.${FROM_ADDRESS}`;
const { makeAccount } = process.env;
const { log, error } = console;

try {
if (makeAccount) {
log("--- Creating and Monitoring LCA ---");

log("Preparing offer...");
const offer = await prepareOffer({
publicInvitationMaker: "createAndMonitorLCA",
instanceName: "axelarGmp",
brandName: "BLD",
amount: 1n,
source: "contract",
offerArgs: {
gasAmount: 0n,
},
});

await processWalletOffer({
offer,
OFFER_FILE,
CONTAINER_PATH,
FROM_ADDRESS,
});
} else {
log("--- Getting EVM Smart Wallet Address ---");

const methodName = "getRemoteAddress";
const invitationArgs = harden([methodName, []]);

log(`Fetching previous offer from ${vStorageUrl}.current`);
const { offerToUsedInvitation } = await fetchFromVStorage(
`${vStorageUrl}.current`,
);
const previousOffer = offerToUsedInvitation[0][0];
log(`Previous offer found: ${JSON.stringify(previousOffer)}`);

log("Preparing offer...");
const offer = await prepareOffer({
invitationMakerName: "makeEVMTransactionInvitation",
instanceName: "axelarGmp",
emptyProposal: true,
source: "continuing",
invitationArgs,
previousOffer,
});

await processWalletOffer({
offer,
OFFER_FILE,
CONTAINER_PATH,
FROM_ADDRESS,
});

const pollIntervalMs = 5000; // 5 seconds
const maxWaitMs = 2 * 60 * 1000; // 2 minutes
const valid = await poll({
checkFn: async () => {
log(`Fetching offer result from ${vStorageUrl}`);
const offerData = await fetchFromVStorage(vStorageUrl);
log(`Offer data received: ${JSON.stringify(offerData)}`);

let smartWalletAddress;
try {
smartWalletAddress = offerData?.status?.result;
} catch (err) {
log("Failed to parse offerData.status.result as JSON:", err);
}

log(`Validating smart wallet address: ${smartWalletAddress}`);
validateEvmAddress(smartWalletAddress);

log(`Smart wallet address: ${smartWalletAddress}`);
return true;
},
pollIntervalMs,
maxWaitMs,
});

if (valid) {
console.log(`✅ Test passed`);
} else {
console.error(`❌ Test failed`);
process.exit(1);
}
}
log("--- Creating and Monitoring LCA ---");

log("Preparing offer...");
const offer = await prepareOffer({
publicInvitationMaker: "createAndMonitorLCA",
instanceName: "axelarGmp",
brandName: "BLD",
amount: 1n,
source: "contract",
});

await processWalletOffer({
offer,
OFFER_FILE,
CONTAINER_PATH,
FROM_ADDRESS,
});
} catch (err) {
error("ERROR:", err.shortMessage || err.message);
process.exit(1);
Expand Down
2 changes: 1 addition & 1 deletion integration/scripts/setup-gmp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { log } = console;

const SDK_REPO = "https://github.com/Agoric/agoric-sdk.git";
const SDK_DIR = "/usr/src/agoric-sdk-cp";
const BRANCH_NAME = "master";
const BRANCH_NAME = "rs-use-nonce-in-axelar-gmp-contract";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this PR supposed to land with this change?

const PLAN_FILE_DIR = "/usr/src/upgrade-test-scripts";
const vbankAssetUrl =
"http://localhost/agoric-lcd/agoric/vstorage/data/published.agoricNames.vbankAsset";
Expand Down
77 changes: 63 additions & 14 deletions packages/axelar-local-dev-cosmos/src/__tests__/Factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe("Factory", () => {
const abiCoder = new ethers.AbiCoder();
const expectedWalletAddress = "0x856e4424f806D16E8CBC702B3c0F2ede5468eae5";

const sourceContract = "agoric";
const sourceChain = "agoric";
const sourceAddress = "0x1234567890123456789012345678901234567890";

let commandIdCounter = 1;
Expand Down Expand Up @@ -75,7 +75,6 @@ describe("Factory", () => {
factory = await Contract.deploy(
axelarGatewayMock.target,
axelarGasServiceMock.target,
"Ethereum",
);
await factory.waitForDeployment();

Expand All @@ -93,7 +92,7 @@ describe("Factory", () => {
});
});

it("fund Factory with ETH to pay for gas", async () => {
it("fund Factory with ETH", async () => {
const provider = ethers.provider;

const factoryAddress = await factory.getAddress();
Expand All @@ -116,7 +115,7 @@ describe("Factory", () => {
return null;
}
})
.find((parsed) => parsed && parsed.name === "Received");
.find((parsed) => parsed && parsed.name === "TokensReceived");

expect(receivedEvent).to.not.be.undefined;
expect(receivedEvent?.args.sender).to.equal(owner.address);
Expand All @@ -129,12 +128,13 @@ describe("Factory", () => {
it("should create a new remote wallet using Factory", async () => {
const commandId = getCommandId();

const payload = abiCoder.encode(["uint256"], [50000]);
const nonce = 1;
const payload = abiCoder.encode(["uint256"], [nonce]);
const payloadHash = keccak256(toBytes(payload));

await approveMessage({
commandId,
from: sourceContract,
from: sourceChain,
sourceAddress,
targetAddress: factory.target,
payload: payloadHash,
Expand All @@ -145,15 +145,64 @@ describe("Factory", () => {

const tx = await factory.execute(
commandId,
sourceContract,
sourceChain,
sourceAddress,
payload,
);

await expect(tx)
.to.emit(factory, "SmartWalletCreated")
.withArgs(expectedWalletAddress, sourceAddress, "agoric", sourceAddress);
await expect(tx).to.emit(factory, "CrossChainCallSent");
.to.emit(factory, "NewWalletCreated")
.withArgs(expectedWalletAddress, nonce, sourceAddress, "agoric");
Comment on lines +154 to +155
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like the gist of it

👍

});

it("should revert if nonce is reused", async () => {
const commandId = getCommandId();

const nonce = 1;
const payload = abiCoder.encode(["uint256"], [nonce]);
const payloadHash = keccak256(toBytes(payload));

await approveMessage({
commandId,
from: sourceChain,
sourceAddress,
targetAddress: factory.target,
payload: payloadHash,
owner,
AxelarGateway: axelarGatewayMock,
abiCoder,
});

await expect(
factory.execute(commandId, sourceChain, sourceAddress, payload),
).to.be.revertedWith("nonce already used by sender");
});

it("should revert if message comes from a chain besides agoric", async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To what extent does this prevent forgery? Do we know that when it comes from Agoric, it's coming from an orchestration-controlled account? Or can anybody with a normal key-based account send such a message?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose if anyone wants to pay for such an account, we don't mind.

const commandId = getCommandId();
const unsupportedSourceChain = "ethereum";
const payload = abiCoder.encode(["uint256"], [2]);
const payloadHash = keccak256(toBytes(payload));

await approveMessage({
commandId,
from: unsupportedSourceChain,
sourceAddress,
targetAddress: factory.target,
payload: payloadHash,
owner,
AxelarGateway: axelarGatewayMock,
abiCoder,
});

await expect(
factory.execute(
commandId,
unsupportedSourceChain,
sourceAddress,
payload,
),
).to.be.revertedWith("Only messages from Agoric chain are allowed");
});

it("should use the remote wallet to call other contracts", async () => {
Expand Down Expand Up @@ -188,7 +237,7 @@ describe("Factory", () => {
const commandId1 = getCommandId();
await approveMessage({
commandId: commandId1,
from: sourceContract,
from: sourceChain,
sourceAddress,
targetAddress: wallet.target,
payload: payloadHash,
Expand All @@ -199,7 +248,7 @@ describe("Factory", () => {

const execTx = await wallet.execute(
commandId1,
sourceContract,
sourceChain,
sourceAddress,
multicallPayload,
);
Expand All @@ -222,7 +271,7 @@ describe("Factory", () => {
const commandId2 = getCommandId();
await approveMessageWithToken({
commandId: commandId2,
from: sourceContract,
from: sourceChain,
sourceAddress,
targetAddress: wallet.target,
payload: payloadHash2,
Expand All @@ -235,7 +284,7 @@ describe("Factory", () => {

const execWithTokenTx = await wallet.executeWithToken(
commandId2,
sourceContract,
sourceChain,
sourceAddress,
multicallPayload2,
"USDC",
Expand Down
Loading