Skip to content

Commit

Permalink
fix(recs): clean values in indexer before using
Browse files Browse the repository at this point in the history
fix(std-client): usage of hasEntity

feat: upgraded so we are at version parody with voxel-aw

replace pnpm with yarn

generate the iworld factory using world/iworld.sol vs iworld.sol

feat: add new createNamespace param to deploy cmd

feat: add optional address

fix: add comments

loudly show errors

estimate gas override

feat: changed default behaviour to mirror the canonical mud

feat: fix estimate gas default val

feat: code to interact with our custom chain

feat: added tenet testnet

revert chain-id to testnet id

minor lockfile update

Update tenetTestnet.ts

add default modules flag

feat(cli): add gas limit

expose getTransactionResult

await tx reduced flag

rm tenet testnet

fix(world): snapsync incorrect index for records
  • Loading branch information
dhvanipa authored and curtischong committed Jul 22, 2023
1 parent 03012e6 commit 0494dab
Show file tree
Hide file tree
Showing 34 changed files with 367 additions and 82 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ yarn.lock
lerna-debug.log
yarn-error.log
.turbo
*.idea

# We don't want projects created from templates to ignore their lockfiles, but we don't
# want to check them in here, so we'll ignore them from the root.
Expand Down
4 changes: 2 additions & 2 deletions e2e/packages/client-vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"nice-grpc-web": "^2.0.1",
"proxy-deep": "^3.1.1",
"react": "^18.2.0",
"rxjs": "7.5.5",
"rxjs": "7.8.1",
"threads": "^1.7.0",
"viem": "1.1.7"
"viem": "1.0.6"
},
"devDependencies": {
"rimraf": "^3.0.2",
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal/packages/client-phaser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"proxy-deep": "^3.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rxjs": "7.5.5",
"rxjs": "7.8.1",
"simplex-noise": "^4.0.1",
"styled-components": "^5.3.10",
"threads": "^1.7.0",
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal/packages/client-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"proxy-deep": "^3.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rxjs": "7.5.5",
"rxjs": "7.8.1",
"threads": "^1.7.0",
"viem": "1.1.7"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal/packages/client-vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"nice-grpc-web": "^2.0.1",
"proxy-deep": "^3.1.1",
"react": "^18.2.0",
"rxjs": "7.5.5",
"rxjs": "7.8.1",
"threads": "^1.7.0",
"viem": "1.1.7"
},
Expand Down
128 changes: 128 additions & 0 deletions integration-sandbox/packages/test/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { chromium, Browser, Page } from "@playwright/test";
import { execa, ExecaChildProcess } from "execa";
import chalk from "chalk";
import { createServer } from "vite";
import type { ViteDevServer } from "vite";

export function startAnvil(port: number): ExecaChildProcess {
return execa("anvil", [
"-b",
"1",
"--block-base-fee-per-gas",
"0",
"--gas-limit",
"20000000",
"--port",
String(port),
]);
}

export function deployContracts(rpc: string): ExecaChildProcess {
const deploymentProcess = execa("yarn", ["mud", "deploy", "--rpc", rpc], { cwd: "../contracts", stdio: "pipe" });
deploymentProcess.stdout?.on("data", (data) => {
console.log(chalk.blueBright("[mud deploy]:"), data.toString());
});

deploymentProcess.stderr?.on("data", (data) => {
console.error(chalk.blueBright("[mud deploy error]:"), data.toString());
});
return deploymentProcess;
}

export async function startViteServer(): Promise<ViteDevServer> {
// TODO this should probably be preview instead of dev server
const mode = "development";
const server = await createServer({
mode,
server: { port: 3000 },
root: "../client-vanilla",
});
await server.listen();
return server;
}

export async function startBrowserAndPage(
reportError: (error: string) => void
): Promise<{ browser: Browser; page: Page }> {
// open browser page
const browser = await chromium.launch();
const page = await browser.newPage();

// log uncaught errors in the browser page (browser and test consoles are separate)
page.on("pageerror", (err) => {
console.log(chalk.yellow("[browser page error]:"), err.message);
});

// log browser's console logs
page.on("console", (msg) => {
if (msg.text().toLowerCase().includes("error")) {
const errorMessage = chalk.yellowBright("[browser error]:") + chalk.red(msg.text());
console.log(errorMessage);
reportError(errorMessage);
} else {
console.log(chalk.yellowBright("[browser console]:"), msg.text());
}
});

return { browser, page };
}

export function syncMODE(reportError: (error: string) => void) {
let resolve: () => void;
let reject: (reason?: string) => void;

console.log(chalk.magenta("[mode]:"), "start syncing");

const modeProcess = execa("./bin/mode", ["-config", "config.mode.yaml"], {
cwd: "../../../packages/services",
stdio: "pipe",
});

modeProcess.on("error", (error) => {
const errorMessage = chalk.magenta("[mode error]:", error);
console.log(errorMessage);
reportError(errorMessage);
reject(errorMessage);
});

modeProcess.stdout?.on("data", (data) => {
const dataString = data.toString();
const errors = extractLineContaining("ERROR", dataString).join("\n");
if (errors) {
console.log(chalk.magenta("[mode error]:", errors));
reject(errors);
}
console.log(chalk.magentaBright("[mode postgres]:", dataString));
});

modeProcess.stderr?.on("data", (data) => {
const dataString = data.toString();
const modeErrors = extractLineContaining("ERROR", dataString).join("\n");
if (modeErrors) {
const errorMessage = chalk.magenta("[mode error]:", modeErrors);
console.log(errorMessage);
reportError(errorMessage);
reject(modeErrors);
}
if (data.toString().includes("finished syncing")) {
console.log(chalk.magenta("[mode]:"), "done syncing");
// Wait for 2s after MODE is done syncing to avoid race conditions
// with the first block number not being available yet
setTimeout(resolve, 2000);
}
console.log(chalk.magentaBright("[mode ingress]:", dataString));
});

return {
doneSyncing: new Promise<void>((res, rej) => {
resolve = res;
reject = rej;
}),
process: modeProcess,
};
}

function extractLineContaining(containing: string, log: string): string[] {
const pattern = new RegExp(`^.*${containing}.*?$`, "gm");
return log.match(pattern) ?? [];
}
7 changes: 7 additions & 0 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export const yDeployOptions = {
saveDeployment: { type: "boolean", desc: "Save the deployment info to a file", default: true },
rpc: { type: "string", desc: "The RPC URL to use. Defaults to the RPC url from the local foundry.toml" },
worldAddress: { type: "string", desc: "Deploy to an existing World at the given address" },
createNamespace: { type: "boolean", desc: "Create and deploy to a new namespace", default: true },
installDefaultModules: { type: "boolean", desc: "Install's default modules", default: true },
estimateGas: {
type: "boolean",
desc: "Estimate gas required before deploying a contract. If false, a high gas estimate is used instead. Useful for deploying large contracts/STORE tables where you can't estimate gas.",
default: false,
},
srcDir: { type: "string", desc: "Source directory. Defaults to foundry src directory." },
disableTxWait: { type: "boolean", desc: "Disable waiting for transactions to be confirmed.", default: false },
pollInterval: {
Expand Down
16 changes: 15 additions & 1 deletion packages/cli/src/commands/dev-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,18 @@ const commandModule: CommandModule<Options, Options> = {
const userHomeDir = homedir();
rmSync(path.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true });

const anvilArgs = ["--block-time", "1", "--block-base-fee-per-gas", "0"];
const anvilArgs = [
"--block-time",
"1",
"--block-base-fee-per-gas",
"0",
"--host",
"0.0.0.0",
"--chain-id",
"31337",
"--gas-limit",
"1000000000",
];
anvil(anvilArgs);
}

Expand Down Expand Up @@ -162,6 +173,9 @@ const commandModule: CommandModule<Options, Options> = {
await deployHandler({
configPath,
skipBuild: true,
createNamespace: true,
installDefaultModules: true,
estimateGas: false,
priorityFeeMultiplier: 1,
disableTxWait: true,
pollInterval: 1000,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/mud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ yargs(hideBin(process.argv))
console.error(chalk.red(msg));
if (msg.includes("Missing required argument")) {
console.log(
chalk.yellow(`Run 'pnpm mud ${process.argv[2]} --help' for a list of available and required arguments.`)
chalk.yellow(`Run 'yarn mud ${process.argv[2]} --help' for a list of available and required arguments.`)
);
}
console.log("");
Expand Down
118 changes: 78 additions & 40 deletions packages/cli/src/utils/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface DeployConfig {
priorityFeeMultiplier: number;
debug?: boolean;
worldAddress?: string;
createNamespace: boolean;
installDefaultModules: boolean;
estimateGas: boolean;
disableTxWait: boolean;
pollInterval: number;
}
Expand All @@ -45,8 +48,19 @@ export async function deploy(

const startTime = Date.now();
const { worldContractName, namespace, postDeployScript } = mudConfig;
const { profile, rpc, privateKey, priorityFeeMultiplier, debug, worldAddress, disableTxWait, pollInterval } =
deployConfig;
const {
profile,
rpc,
privateKey,
priorityFeeMultiplier,
debug,
worldAddress,
createNamespace,
installDefaultModules,
estimateGas,
disableTxWait,
pollInterval,
} = deployConfig;
const forgeOutDirectory = await getOutDirectory(profile);

// Set up signer for deployment
Expand Down Expand Up @@ -89,42 +103,57 @@ export async function deploy(
);

// Deploy default World modules
const defaultModules: Record<string, Promise<string>> = {
// TODO: these only need to be deployed once per chain, add a check if they exist already
CoreModule: deployContract(CoreModuleData.abi, CoreModuleData.bytecode, disableTxWait, "CoreModule"),
KeysWithValueModule: deployContract(
KeysWithValueModuleData.abi,
KeysWithValueModuleData.bytecode,
disableTxWait,
"KeysWithValueModule"
),
KeysInTableModule: deployContract(
KeysInTableModuleData.abi,
KeysInTableModuleData.bytecode,
disableTxWait,
"KeysInTableModule"
),
UniqueEntityModule: deployContract(
UniqueEntityModuleData.abi,
UniqueEntityModuleData.bytecode,
disableTxWait,
"UniqueEntityModule"
),
SnapSyncModule: deployContract(
SnapSyncModuleData.abi,
SnapSyncModuleData.bytecode,
disableTxWait,
"SnapSyncModule"
),
};
const defaultModules: Record<string, Promise<string>> = !installDefaultModules
? {}
: {
// TODO: these only need to be deployed once per chain, add a check if they exist already
CoreModule: deployContract(CoreModuleData.abi, CoreModuleData.bytecode, disableTxWait, "CoreModule"),
KeysWithValueModule: deployContract(
KeysWithValueModuleData.abi,
KeysWithValueModuleData.bytecode,
disableTxWait,
"KeysWithValueModule"
),
KeysInTableModule: deployContract(
KeysInTableModuleData.abi,
KeysInTableModuleData.bytecode,
disableTxWait,
"KeysInTableModule"
),
UniqueEntityModule: deployContract(
UniqueEntityModuleData.abi,
UniqueEntityModuleData.bytecode,
disableTxWait,
"UniqueEntityModule"
),
SnapSyncModule: deployContract(
SnapSyncModuleData.abi,
SnapSyncModuleData.bytecode,
disableTxWait,
"SnapSyncModule"
),
};

// Deploy user Modules
const modulePromises = mudConfig.modules
.filter((module) => !defaultModules[module.name]) // Only deploy user modules here, not default modules
.reduce<Record<string, Promise<string>>>((acc, module) => {
acc[module.name] = deployContractByName(module.name, disableTxWait);
return acc;
}, defaultModules);

// Note: when creating a new namespace, we want some default modules
// However, when deploying to an existing one, we may not want those (since they'd be already deployed)
// Over here, we enforce that when deploying to an existing namespace, you CANNOT deploy a new module
// and have to use an existing one, by passing in the module address
// TODO: However, a better way to do this is to check if the module exists, and if not, deploy it,
// otherwise, get the address of it and simply use that
const modulePromises = !createNamespace
? mudConfig.modules.reduce<Record<string, Promise<string>>>((acc, module) => {
console.log(chalk.blue(`Using existing deployed module ${module.name}...`));
acc[module.name] = Promise.resolve(module.address);
return acc;
}, {})
: mudConfig.modules
.filter((module) => !defaultModules[module.name]) // Only deploy user modules here, not default modules
.reduce<Record<string, Promise<string>>>((acc, module) => {
acc[module.name] = deployContractByName(module.name, disableTxWait);
return acc;
}, defaultModules);

// Combine all contracts into one object
const contractPromises: Record<string, Promise<string>> = { ...worldPromise, ...systemPromises, ...modulePromises };
Expand All @@ -142,7 +171,12 @@ export async function deploy(
}

// Register namespace
if (namespace) await fastTxExecute(WorldContract, "registerNamespace", [toBytes16(namespace)], confirmations);
if (createNamespace && namespace) {
console.log(chalk.blue(`Registering namespace ${namespace}`));
await fastTxExecute(WorldContract, "registerNamespace", [toBytes16(namespace)], confirmations);
} else {
console.log(chalk.yellow("Skipping namespace registration"));
}

// Register tables
const tableIds: { [tableName: string]: Uint8Array } = {};
Expand Down Expand Up @@ -494,15 +528,19 @@ export async function deploy(
): Promise<Awaited<ReturnType<Awaited<ReturnType<C[F]>>["wait"]>>> {
const functionName = `${func as string}(${args.map((arg) => `'${arg}'`).join(",")})`;
try {
const gasLimit = await contract.estimateGas[func].apply(null, args);
// We are manually specifying a high gasLimit since deploying large STORE tables is expensive
// and the estimateGas function cannot properly estimate gas usage
// Since this is used in the cli only for deployment, it's okay to use a high gasLimit
const gasLimit = estimateGas ? await contract.estimateGas[func].apply(null, args) : 10_000_000;

console.log(chalk.gray(`executing transaction: ${functionName} with nonce ${nonce}`));
const txPromise = contract[func]
.apply(null, [...args, { gasLimit, nonce: nonce++, maxPriorityFeePerGas, maxFeePerGas }])
.then((tx: any) => (confirmations === 0 ? tx : tx.wait(confirmations)));
promises.push(txPromise);
return txPromise;
} catch (error: any) {
if (debug) console.error(error);
console.error(error);
if (retryCount === 0 && error?.message.includes("transaction already imported")) {
// If the deployment failed because the transaction was already imported,
// retry with a higher priority fee
Expand Down Expand Up @@ -545,7 +583,7 @@ export async function deploy(
if (!feeData.lastBaseFeePerGas) throw new MUDError("Can not fetch lastBaseFeePerGas from RPC");
if (!feeData.lastBaseFeePerGas.eq(0) && (await signer.getBalance()).eq(0)) {
throw new MUDError(`Attempting to deploy to a chain with non-zero base fee with an account that has no balance.
If you're deploying to the Lattice testnet, you can fund your account by running 'pnpm mud faucet --address ${await signer.getAddress()}'`);
If you're deploying to the Lattice testnet, you can fund your account by running 'yarn mud faucet --address ${await signer.getAddress()}'`);
}

// Set the priority fee to 0 for development chains with no base fee, to allow transactions from unfunded wallets
Expand Down
Loading

0 comments on commit 0494dab

Please sign in to comment.