Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix e2e tests #99

Open
wants to merge 13 commits into
base: dev
Choose a base branch
from
517 changes: 401 additions & 116 deletions apps/agent/test/e2e/scenarios/01_happy_path/index.spec.ts

Large diffs are not rendered by default.

149 changes: 137 additions & 12 deletions apps/agent/test/e2e/utils/prophet-e2e-scaffold/eboCore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import assert from "assert";
import { bondEscalationModuleAbi } from "@ebo-agent/automated-dispute/src/abis/index.js";

Check failure on line 2 in apps/agent/test/e2e/utils/prophet-e2e-scaffold/eboCore.ts

View workflow job for this annotation

GitHub Actions / lint / Run Linters

'bondEscalationModuleAbi' is defined but never used
import { Caip2ChainId } from "@ebo-agent/shared";
import { execa } from "execa";
import {
Expand All @@ -9,7 +10,6 @@
createTestClient,
createWalletClient,
encodeFunctionData,
formatEther,
Hex,
http,
HttpTransport,
Expand All @@ -19,6 +19,7 @@
walletActions,
} from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { simulateContract } from "viem/actions";

Check failure on line 22 in apps/agent/test/e2e/utils/prophet-e2e-scaffold/eboCore.ts

View workflow job for this annotation

GitHub Actions / lint / Run Linters

'simulateContract' is defined but never used

import { AnvilClient } from "./anvil.js";

Expand Down Expand Up @@ -72,8 +73,96 @@
});

console.log(`Added 1 ETH to ${account.address}.`);
const minter = "0xb4df23e4bafc17cda0b0e4227f0ce09036969927";

await anvilClient.setBalance({
address: minter,
value: parseEther("100"),
});

console.log("funded minter with eth");
const balanceMinterEth = await anvilClient.getBalance({
address: minter,
});
console.log(balanceMinterEth, "balanceMinterEth");

await anvilClient.stopImpersonatingAccount({ address: grt.holderAddress });

await anvilClient.impersonateAccount({
address: minter,
});

console.log("funded minter with eth");

console.log(`Sending GRT tokens from ${grt.holderAddress} to ${account.address}...`);
console.log(grt.holderAddress, "grt.holderAddress");
console.log(minter, "minter address");

// Add before minting
const isMinter = await anvilClient.readContract({
address: grt.contractAddress,
abi: parseAbi(["function isMinter(address) returns (bool)"]),
functionName: "isMinter",
args: [minter],
});

console.log(`Is minter authorized? ${isMinter}`);

if (!isMinter) {
throw new Error(`Address ${minter} does not have minting permissions`);
}

// simulate minting grt

console.log("simulating");
const request = await anvilClient.simulateContract({
address: grt.contractAddress,
abi: parseAbi(["function mint(address, uint256)"]),
functionName: "mint",
args: [grt.holderAddress, grt.fundAmount],
account: minter,
});

console.log(request, "mint request logged");

// After minting
const mintHash = await anvilClient.sendTransaction({
account: minter,
to: grt.contractAddress,
data: encodeFunctionData({
abi: parseAbi(["function mint(address, uint256)"]),
args: [grt.holderAddress, grt.fundAmount],
}),
});

// Wait for transaction receipt
const mintReceipt = await anvilClient.waitForTransactionReceipt({ hash: mintHash });
console.log("Mint transaction receipt:", mintReceipt);

// Check if mint was successful by verifying the holder's balance
const balanceAfterMint = await anvilClient.readContract({
address: grt.contractAddress,
abi: parseAbi(["function balanceOf(address) returns (uint256)"]),
functionName: "balanceOf",
args: [grt.holderAddress],
});

console.log(`GRT balance after minting: ${balanceAfterMint}`);

if (balanceAfterMint === 0n) {
throw new Error(`Minting failed - no balance for holder ${grt.holderAddress}`);
}

await anvilClient.stopImpersonatingAccount({ address: minter });

await anvilClient.impersonateAccount({
address: grt.holderAddress,
});

console.log(grt.holderAddress, "grt.holderAddress");
console.log(grt.contractAddress, "grt.contractAddress");
console.log(grt.fundAmount, "grt.fundAmount");
console.log(account.address, "account.address");

const hash = await anvilClient.sendTransaction({
account: grt.holderAddress,
Expand Down Expand Up @@ -331,45 +420,82 @@
anvilClient: AnvilClient<HttpTransport, Chain, undefined>,
) {
console.log("Staking GRT into Horizon...");

const { grt, horizonStaking, horizonAccountingExtension } = addresses;

for (const account of accounts) {
console.log(`Approving GRT txs on ${account.address} to ${horizonStaking}`);
// 1. First approve GRT spending
console.log(`Approving GRT spending for ${account.address}`);

const approveHash = await anvilClient.sendTransaction({
account: account,
to: grt,
data: encodeFunctionData({
abi: parseAbi(["function approve(address, uint256)"]),
abi: parseAbi(["function approve(address spender, uint256 amount)"]),
args: [horizonStaking, grtProvisionAmount],
}),
});

await anvilClient.waitForTransactionReceipt({
hash: approveHash,
confirmations: 1,
});

// 2. Verify GRT allowance
const allowance = await anvilClient.readContract({
address: grt,
abi: parseAbi(["function allowance(address,address) view returns (uint256)"]),
functionName: "allowance",
args: [account.address, horizonStaking],
});

console.log(`Staking for ${account.address} ${formatEther(grtProvisionAmount)} GRT...`);
console.log(`GRT allowance for staking: ${allowance}`);

// 3. Stake GRT tokens
console.log("Staking GRT...");
console.log(grtProvisionAmount, "grtProvisionAmount");
const stakeHash = await anvilClient.sendTransaction({
account: account,
to: horizonStaking,
data: encodeFunctionData({
abi: parseAbi(["function stake(uint256)"]),
abi: parseAbi(["function stake(uint256 amount)"]),
args: [grtProvisionAmount],
}),
});

await anvilClient.waitForTransactionReceipt({
const stakeReceipt = await anvilClient.waitForTransactionReceipt({
hash: stakeHash,
confirmations: 1,
});

console.log("Stake transaction receipt:", stakeReceipt);

// 4. Mine a block to update state
await anvilClient.mine({ blocks: 1 });

// 5. Now check staked amount
console.log("Checking staked amount...");
const stakedAmount = await anvilClient.readContract({
address: horizonStaking,
abi: parseAbi(["function stakes(address) view returns (uint256)"]),
functionName: "stakes",
args: [account.address],
});

console.log(`Staked amount: ${stakedAmount}`);

if (stakedAmount < grtProvisionAmount) {
throw new Error(`Failed to stake required amount for ${account.address}`);
}

// 6. Then proceed with provision setup
console.log("Setting up provision...");
const provisionHash = await anvilClient.sendTransaction({
account: account,
to: horizonStaking,
data: encodeFunctionData({
abi: parseAbi(["function provision(address, address, uint256, uint32, uint64)"]),
abi: parseAbi([
"function provision(address account, address extension, uint256 tokens, uint32 allocationId, uint64 period)",
]),
args: [
account.address,
horizonAccountingExtension,
Expand All @@ -378,16 +504,15 @@
// https://github.com/defi-wonderland/EBO-core/blob/175bcd57c3254a90dd6fcbf53b3db3359085551f/src/contracts/HorizonAccountingExtension.sol#L38C26-L38C42
1_000_000,
// https://github.com/defi-wonderland/EBO-core/blob/175bcd57c3254a90dd6fcbf53b3db3359085551f/script/Constants.sol#L21
BigInt(60 * 60 * 24 * 3), // 3 days
BigInt(60 * 60 * 24 * 3), // 3 days in seconds
],
}),
});

await anvilClient.waitForTransactionReceipt({
const provisionReceipt = await anvilClient.waitForTransactionReceipt({
hash: provisionHash,
});

console.log(`Stake and provision done for ${account.address}`);
console.log("Provision transaction receipt:", provisionReceipt);
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/automated-dispute/src/exceptions/errorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ const errorStrategies = new Map<ErrorName, ErrorHandlingStrategy>(errorStrategie

export class ErrorFactory {
public static createError(errorName: ErrorName | string): CustomContractError {
console.error(`ErrorFactory.createError: ${errorName}`);
if (!errorStrategies.has(errorName as ErrorName)) {
return new CustomContractError("UnknownError", {
shouldNotify: true,
Expand Down
27 changes: 20 additions & 7 deletions packages/automated-dispute/src/providers/protocolProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BlockNumberService, UnsupportedChain } from "@ebo-agent/blocknumber";
import { Caip2ChainId, HexUtils, ILogger, UnixTimestamp } from "@ebo-agent/shared";
import { Caip2ChainId, chainIdHashMap, HexUtils, ILogger, UnixTimestamp } from "@ebo-agent/shared";
import {
Account,
Address,
Expand All @@ -14,7 +14,6 @@ import {
getContract,
GetContractReturnType,
Hex,
hexToString,
http,
HttpTransport,
Log,
Expand Down Expand Up @@ -797,14 +796,21 @@ export class ProtocolProvider implements IProtocolProvider {
*/
async getAvailableChains(): Promise<Caip2ChainId[]> {
try {
const allowedChainIdsBytes32 =
const allowedChainIdsBytes32: ReadonlyArray<Hex> =
await this.eboRequestCreatorContract.read.getAllowedChainIds();

const allowedChainIds: Caip2ChainId[] = allowedChainIdsBytes32.map(
(bytes32) =>
hexToString(bytes32 as `0x${string}`).replace(/\0/g, "") as Caip2ChainId,
);
const allowedChainIds: Caip2ChainId[] = [];

allowedChainIdsBytes32.forEach((bytes32) => {
const normalizedHash = bytes32.toLowerCase();
const originalChainId = chainIdHashMap[normalizedHash];

if (originalChainId) {
allowedChainIds.push(originalChainId as Caip2ChainId);
} else {
this.logger.warn(`Unknown chain ID hash encountered: ${bytes32}`);
}
});
return allowedChainIds;
} catch (error) {
throw new FetchAvailableChainsError();
Expand Down Expand Up @@ -1229,6 +1235,13 @@ export class ProtocolProvider implements IProtocolProvider {
verifier: Address,
operator: Address,
): Promise<boolean> {
const normalizedServiceProvider = serviceProvider.toLowerCase();
const normalizedOperator = operator.toLowerCase();

// If the service provider and operator are the same, skip authorization check
if (normalizedServiceProvider === normalizedOperator) {
return true;
}
return await this.horizonStakingContract.read.isAuthorized([
serviceProvider,
verifier,
Expand Down
2 changes: 1 addition & 1 deletion packages/automated-dispute/src/services/eboProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ export class EboProcessor {
const availableChains: Caip2ChainId[] =
await this.protocolProvider.getAvailableChains();

this.logger.info("Available chains fetched.");
this.logger.info("Available chains fetched.", stringify(availableChains));

const handledChainIds = this.getHandledChainIds(epoch) || new Set();

Expand Down
32 changes: 10 additions & 22 deletions packages/automated-dispute/tests/services/protocolProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
getContract,
Hex,
http,
keccak256,
toBytes,
WaitForTransactionReceiptTimeoutError,
} from "viem";
import { privateKeyToAccount, privateKeyToAddress } from "viem/accounts";
Expand Down Expand Up @@ -1228,6 +1230,12 @@ describe("ProtocolProvider", () => {
});

describe("getAvailableChains", () => {
const ARBITRUM_SEPOLIA_ID = "eip155:421614" as const;
const ETHEREUM_ID = "eip155:1" as const;

const ETHEREUM_ID_HASH = keccak256(toBytes(ETHEREUM_ID)).toLowerCase();
const ARBITRUM_SEPOLIA_ID_HASH = keccak256(toBytes(ARBITRUM_SEPOLIA_ID)).toLowerCase();

it("successfully retrieves available chains", async () => {
const protocolProvider = new ProtocolProvider(
mockRpcConfigBase,
Expand All @@ -1238,16 +1246,13 @@ describe("ProtocolProvider", () => {
mockBlockNumberService,
);

const mockAllowedChainIdsBytes32: `0x${string}`[] = [
"0x6569703135353a31000000000000000000000000000000000000000000000000",
"0x6569703135353a34323136313400000000000000000000000000000000000000",
];
const mockAllowedChainIdsBytes32 = [ETHEREUM_ID_HASH, ARBITRUM_SEPOLIA_ID_HASH];

(
protocolProvider["eboRequestCreatorContract"].read.getAllowedChainIds as Mock
).mockResolvedValue(mockAllowedChainIdsBytes32);

const expectedChains: Caip2ChainId[] = ["eip155:1", "eip155:421614"];
const expectedChains: Caip2ChainId[] = [ETHEREUM_ID, ARBITRUM_SEPOLIA_ID];

const chains = await protocolProvider.getAvailableChains();

Expand Down Expand Up @@ -1299,23 +1304,6 @@ describe("ProtocolProvider", () => {
});
});

describe("getAccountingModuleAddress", () => {
it("returns the correct accounting module address", () => {
const protocolProvider = new ProtocolProvider(
mockRpcConfigBase,
mockContractAddress,
mockedPrivateKey,
mockServiceProviderAddress,
mockLogger(),
mockBlockNumberService,
);

expect(protocolProvider.getAccountingModuleAddress()).toBe(
mockContractAddress.horizonAccountingExtension,
);
});
});

describe("Service Provider Address", () => {
it("uses the provided serviceProviderAddress from config", () => {
const protocolProvider = new ProtocolProvider(
Expand Down
20 changes: 20 additions & 0 deletions packages/shared/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { keccak256, toBytes } from "viem";

import { Caip2ChainId } from "./types/index.js";

/** Supported chains on EBO organized by namespaces and their references */
Expand Down Expand Up @@ -33,3 +35,21 @@ export const EBO_SUPPORTED_CHAIN_IDS = Object.values(EBO_SUPPORTED_CHAINS_CONFIG
},
[] as Caip2ChainId[],
);

/**
* Generates a mapping between keccak256 hashes of chain IDs and their original string representations.
*
* @returns {Record<string, Caip2ChainId>} A mapping from lowercase bytes32 hash strings to their corresponding CAIP-2 chain IDs.
*/
export function generateChainIdHashMap(): Record<string, Caip2ChainId> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Check Caip2Utils.findByHash, it might work for your use case here

const map: Record<string, Caip2ChainId> = {};

EBO_SUPPORTED_CHAIN_IDS.forEach((chainId) => {
const hashValue = keccak256(toBytes(chainId)).toLowerCase();
map[hashValue] = chainId;
});

return map;
}

export const chainIdHashMap = generateChainIdHashMap();
Loading