Skip to content

Commit

Permalink
l1 cheatcode (#1428)
Browse files Browse the repository at this point in the history
Fix #1288 & Move cheatcodes to fixtures

# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [ ] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [ ] Every change is related to the PR description.
- [ ] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).
  • Loading branch information
rahul-kothari authored Aug 9, 2023
1 parent ac935e1 commit 6688ca7
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 9 deletions.
79 changes: 76 additions & 3 deletions yarn-project/end-to-end/src/e2e_cheat_codes.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { AztecNodeService } from '@aztec/aztec-node';
import { AztecRPCServer } from '@aztec/aztec-rpc';
import { AztecRPCServer, EthAddress } from '@aztec/aztec-rpc';
import { AztecRPC } from '@aztec/types';

import { CheatCodes } from './cheat_codes.js';
import { Account, Chain, HttpTransport, PublicClient, WalletClient, parseEther } from 'viem';

import { CheatCodes } from './fixtures/cheat_codes.js';
import { setup } from './fixtures/utils.js';

describe('e2e_cheat_codes', () => {
let aztecNode: AztecNodeService | undefined;
let aztecRpcServer: AztecRPC;

let cc: CheatCodes;
let walletClient: WalletClient<HttpTransport, Chain, Account>;
let publicClient: PublicClient<HttpTransport, Chain>;

beforeAll(async () => {
({ aztecNode, aztecRpcServer, cheatCodes: cc } = await setup());
let deployL1ContractsValues;
({ aztecNode, aztecRpcServer, cheatCodes: cc, deployL1ContractsValues } = await setup());
walletClient = deployL1ContractsValues.walletClient;
publicClient = deployL1ContractsValues.publicClient;
}, 100_000);

afterAll(async () => {
Expand Down Expand Up @@ -49,5 +56,71 @@ describe('e2e_cheat_codes', () => {
expect(await cc.l1.blockNumber()).toBe(blockNumber + 1);
expect(await cc.l1.timestamp()).toBe(timestamp + increment);
});

it('load a value at a particular storage slot', async () => {
// check that storage slot 0 is empty as expected
const res = await cc.l1.load(EthAddress.ZERO, 0n);
expect(res).toBe(0n);
});

it.each(['1', 'bc40fbf4394cd00f78fae9763b0c2c71b21ea442c42fdadc5b720537240ebac1'])(
'store a value at a given slot and its keccak value of the slot (if it were in a map) ',
async storageSlotInHex => {
const storageSlot = BigInt('0x' + storageSlotInHex);
const valueToSet = 5n;
const contractAddress = EthAddress.fromString('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266');
await cc.l1.store(contractAddress, storageSlot, valueToSet);
expect(await cc.l1.load(contractAddress, storageSlot)).toBe(valueToSet);
// also test with the keccak value of the slot - can be used to compute storage slots of maps
await cc.l1.store(contractAddress, cc.l1.keccak256(0n, storageSlot), valueToSet);
expect(await cc.l1.load(contractAddress, cc.l1.keccak256(0n, storageSlot))).toBe(valueToSet);
},
);

it('set bytecode correctly', async () => {
const contractAddress = EthAddress.fromString('0x70997970C51812dc3A010C7d01b50e0d17dc79C8');
await cc.l1.etch(contractAddress, '0x1234');
expect(await cc.l1.getBytecode(contractAddress)).toBe('0x1234');
});

it('impersonate', async () => {
// we will transfer 1 eth to a random address. Then impersonate the address to be able to send funds
// without impersonation we wouldn't be able to send funds.
const myAddress = (await walletClient.getAddresses())[0];
const randomAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
await walletClient.sendTransaction({
account: myAddress,
to: randomAddress,
value: parseEther('1'),
});
const beforeBalance = await publicClient.getBalance({ address: randomAddress });

// impersonate random address
await cc.l1.startPrank(EthAddress.fromString(randomAddress));
// send funds from random address
const amountToSend = parseEther('0.1');
await walletClient.sendTransaction({
account: randomAddress,
to: myAddress,
value: amountToSend,
});
expect(await publicClient.getBalance({ address: randomAddress })).toBeLessThan(beforeBalance - amountToSend); // account for fees too

// stop impersonating
await cc.l1.stopPrank(EthAddress.fromString(randomAddress));

// making calls from random address should not be successful
try {
await walletClient.sendTransaction({
account: randomAddress,
to: myAddress,
value: amountToSend,
});
// done with a try-catch because viem errors are noisy and we need to check just a small portion of the error.
fail('should not be able to send funds from random address');
} catch (e: any) {
expect(e.message).toContain('No Signer available');
}
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AztecAddress, CircuitsWasm, Fr } from '@aztec/circuits.js';
import { AztecAddress, CircuitsWasm, EthAddress, Fr } from '@aztec/circuits.js';
import { pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg';
import { toBigIntBE, toHex } from '@aztec/foundation/bigint-buffer';
import { keccak } from '@aztec/foundation/crypto';
import { createDebugLogger } from '@aztec/foundation/log';
import { AztecRPC } from '@aztec/types';

Expand Down Expand Up @@ -127,8 +129,83 @@ export class L1CheatCodes {
this.logger(`Loaded state from ${fileName}`);
}

// Good basis for the remaining functions:
// https://github.com/foundry-rs/foundry/blob/master/anvil/core/src/eth/mod.rs
/**
* Load the value at a storage slot of a contract address on L1
* @param contract - The contract address
* @param slot - The storage slot
* @returns - The value at the storage slot
*/
public async load(contract: EthAddress, slot: bigint): Promise<bigint> {
const res = await this.rpcCall('eth_getStorageAt', [contract.toString(), toHex(slot), 'latest']);
return BigInt(res.result);
}

/**
* Set the value at a storage slot of a contract address on L1
* @param contract - The contract address
* @param slot - The storage slot
* @param value - The value to set the storage slot to
*/
public async store(contract: EthAddress, slot: bigint, value: bigint): Promise<void> {
// for the rpc call, we need to change value to be a 32 byte hex string.
const res = await this.rpcCall('anvil_setStorageAt', [contract.toString(), toHex(slot), toHex(value, true)]);
if (res.error) throw new Error(`Error setting storage for contract ${contract} at ${slot}: ${res.error.message}`);
this.logger(`Set storage for contract ${contract} at ${slot} to ${value}`);
}

/**
* Computes the slot value for a given map and key.
* Both the baseSlot and key will be padded to 32 bytes in the function.
* @param baseSlot - The base slot of the map (specified in noir contract)
* @param key - The key to lookup in the map
* @returns The storage slot of the value in the map
*/
public keccak256(baseSlot: bigint, key: bigint): bigint {
// abi encode (removing the 0x) - concat key and baseSlot (both padded to 32 bytes)
const abiEncoded = toHex(key, true).substring(2) + toHex(baseSlot, true).substring(2);
return toBigIntBE(keccak(Buffer.from(abiEncoded, 'hex')));
}

/**
* Send transactions impersonating an externally owned account or contract.
* @param who - The address to impersonate
*/
public async startPrank(who: EthAddress): Promise<void> {
const res = await this.rpcCall('anvil_impersonateAccount', [who.toString()]);
if (res.error) throw new Error(`Error pranking ${who}: ${res.error.message}`);
this.logger(`Impersonating ${who}`);
}

/**
* Stop impersonating an account that you are currently impersonating.
* @param who - The address to stop impersonating
*/
public async stopPrank(who: EthAddress): Promise<void> {
const res = await this.rpcCall('anvil_stopImpersonatingAccount', [who.toString()]);
if (res.error) throw new Error(`Error pranking ${who}: ${res.error.message}`);
this.logger(`Stopped impersonating ${who}`);
}

/**
* Set the bytecode for a contract
* @param contract - The contract address
* @param bytecode - The bytecode to set
*/
public async etch(contract: EthAddress, bytecode: `0x${string}`): Promise<void> {
const res = await this.rpcCall('anvil_setCode', [contract.toString(), bytecode]);
if (res.error) throw new Error(`Error setting bytecode for ${contract}: ${res.error.message}`);
this.logger(`Set bytecode for ${contract} to ${bytecode}`);
}

/**
* Get the bytecode for a contract
* @param contract - The contract address
* @returns The bytecode for the contract
*/
public async getBytecode(contract: EthAddress): Promise<`0x${string}`> {
const res = await this.rpcCall('eth_getCode', [contract.toString(), 'latest']);
return res.result;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { AztecRPC, TxStatus } from '@aztec/types';

import { Chain, HttpTransport, PublicClient, getContract } from 'viem';

import { CheatCodes } from '../cheat_codes.js';
import { CheatCodes } from './cheat_codes.js';
import { deployAndInitializeNonNativeL2TokenContracts } from './utils.js';

/**
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
} from 'viem';
import { mnemonicToAccount } from 'viem/accounts';

import { CheatCodes, L1CheatCodes } from '../cheat_codes.js';
import { CheatCodes, L1CheatCodes } from './cheat_codes.js';
import { MNEMONIC, localAnvil } from './fixtures.js';

const { SANDBOX_URL = '' } = process.env;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AztecRPC, TxStatus } from '@aztec/types';

import { getContract, parseEther } from 'viem';

import { CheatCodes } from './cheat_codes.js';
import { CheatCodes } from './fixtures/cheat_codes.js';
import { CrossChainTestHarness } from './fixtures/cross_chain_test_harness.js';
import { delay, deployAndInitializeNonNativeL2TokenContracts, setup } from './fixtures/utils.js';

Expand Down

0 comments on commit 6688ca7

Please sign in to comment.