Skip to content

Commit

Permalink
Better feedback on the JSON response
Browse files Browse the repository at this point in the history
  • Loading branch information
carletex committed Sep 10, 2022
1 parent deede00 commit e8d69e9
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 86 deletions.
14 changes: 10 additions & 4 deletions challenges.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@
"id": 0,
"name": "challenge-0-simple-nft",
"github": "https://github.com/scaffold-eth/scaffold-eth-challenges.git",
"contractName": "YourCollectible"
"contractName": "YourCollectible",
"telegram": "https://t.me/+Y2vqXZZ_pEFhMGMx"
},
{
"id": 1,
"name": "challenge-1-decentralized-staking",
"github": "https://github.com/scaffold-eth/scaffold-eth-challenges.git",
"contractName": "Staker"
"contractName": "Staker",
"telegram": "https://t.me/joinchat/E6r91UFt4oMJlt01"
},
{
"id": 2,
"name": "challenge-2-token-vendor",
"github": "https://github.com/scaffold-eth/scaffold-eth-challenges.git",
"contractName": "Vendor"
"contractName": "Vendor",
"telegram": "https://t.me/joinchat/IfARhZFc5bfPwpjq",
"successMessage": "<p>You have successfully passed challenge 2! Keep it up with Challenge 3 - Dice Game!</p><p>--</p>"
},
{
"id": 3,
"name": "challenge-3-dice-game",
"github": "https://github.com/scaffold-eth/scaffold-eth-challenges.git",
"contractName": "RiggedRoll"
"contractName": "RiggedRoll",
"telegram": "https://t.me/+3StA0aBSArFjNjUx",
"successMessage": "<p>This looks good! Demo site and contract code are solid and the dice only roll when it’s a winner!</p><p>You have passed the first four challenges on SpeedRunEthereum and can now join the BuildGuidl! Head to your profile on SpeedRunEthereum.com and use the link to join. This will also unlock the more challenging Decentralized Exchange, Multisig, and SVG NFT challenges. These are more open ended which, once complete, can be submitted as a build on your buidlguidl.com portfolio to show off your web3 knowledge!</p><p>Join the next challenge’s Telegram channel on SpeedRunEthereum to get started. This channel includes other builders and BuidlGuild members to guide you along the way</p><p>--</p>"
}
]
94 changes: 12 additions & 82 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const bodyParser = require("body-parser");
const app = express();
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const { copyContractFromEtherscan } = require("./utils/contract");
const { allowedNetworks } = require("./utils/config");
const { downloadAndTestContract } = require("./utils/contract");
const { MESSAGES } = require("./utils/messages");

let challenges = JSON.parse(fs.readFileSync("challenges.json").toString());

Expand All @@ -16,42 +16,6 @@ app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Run tests for a remote {address} contract, for a {challenge} in {network}.
const testChallenge = async ({ challenge, network, address }) => {
const result = {
challenge: challenge.name,
network,
address,
};
try {
console.log("====] RUNNING " + challenge.name + "[==============]");

const { stdout } = await exec(
"cd " +
challenge.name +
" && CONTRACT_ADDRESS=" +
address +
" yarn test --network hardhat"
);

console.log("Tests passed successfully!\n");
result.success = true;
// Maybe we don't want this when succeeding.
result.feedback = stdout;
} catch (e) {
console.error("Test failed", JSON.stringify(e), "\n");

result.success = false;
// ToDo. Parse this and gives a better feedback.
result.feedback = e.stdout + "\n\n" + e.stderr;
}

// Delete files. Don't need to await.
exec(`rm ${challenge.name}/packages/hardhat/contracts/${address}.sol`);

return result;
};

app.get("/", async function (req, res) {
res.status(200).send("HELLO WORLD!");
});
Expand All @@ -72,36 +36,21 @@ app.get("/:challengeId/:network/:address", async function (req, res) {
const network = req.params.network;
const address = req.params.address;

if (!challenges[challengeId]) {
// Challenge not found.
return res.sendStatus(404);
}

const challenge = challenges[challengeId];

let result;
try {
await copyContractFromEtherscan(network, address, challenge.id);
result = await downloadAndTestContract(challengeId, network, address);
} catch (e) {
console.error(
`❌❌ Can't get the contract from ${network} in ${address}.`,
e.message
);
return res.status(200).send(`
<html>
<body>
<pre>
<br/>Can't get the contract from ${network} in ${address}.<br/><br/>${e.message}
</pre>
<p>Can't get the contract from ${network} in ${address}.</p>
<p>${e.message}</p>${MESSAGES.telegramHelp(challenges[challengeId])}
</body>
</html>
`);
}

const result = await testChallenge({ challenge, network, address });

return res
.status(200)
.send("<html><body><pre>" + result.feedback + "</pre></body></html>");
return res.status(200).send(`<html><body>${result.feedback}</body></html>`);
});

// Main API endpoint.
Expand All @@ -111,36 +60,17 @@ app.post("/", async function (req, res) {
const network = req.body.network;
const address = req.body.address;

if (!challenges[challengeId]) {
// Challenge not found.
return res.sendStatus(404);
}

if (!allowedNetworks.includes(network)) {
console.error(`"${network}" is not a valid testnet.`);
// Network not allowed
return res
.status(404)
.json({ error: `"${network}" is not a valid testnet.` });
}

console.log(`📡 Downloading contract from ${network}`);

let result;
try {
await copyContractFromEtherscan(network, address, challengeId);
result = await downloadAndTestContract(challengeId, network, address);
} catch (e) {
console.error(
`❌❌ Can't get the contract from ${network} in ${address}.`,
e.message
);
return res.status(404).json({
error: `Can't get the contract from ${network} in ${address}.\n\n${e.message}`,
error: `<p>Can't get the contract from ${network} in ${address}.</p><p>${
e.message
}</p>${MESSAGES.telegramHelp(challenges[challengeId])}`,
});
}

const challenge = challenges[challengeId];
const result = await testChallenge({ challenge, network, address });

return res.json(result);
});

Expand Down
63 changes: 63 additions & 0 deletions utils/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ require("dotenv").config();
const axios = require("axios");
const { allowedNetworks, ETHERSCAN_API_KEY } = require("./config");
const fs = require("fs");
const util = require("util");
const { MESSAGES } = require("./messages");
const exec = util.promisify(require("child_process").exec);

let challenges = JSON.parse(fs.readFileSync("challenges.json").toString());

Expand Down Expand Up @@ -64,7 +67,67 @@ const copyContractFromEtherscan = async (network, address, challengeId) => {
}
};

// Run tests for a remote {address} contract, for a {challenge} in {network}.
const testChallenge = async ({ challenge, network, address }) => {
const result = {
challenge: challenge.name,
network,
address,
};
try {
console.log("====] RUNNING " + challenge.name + "[==============]");

const { stdout } = await exec(
"cd " +
challenge.name +
" && CONTRACT_ADDRESS=" +
address +
" yarn test --network hardhat"
);

console.log("Tests passed successfully!\n");
result.success = true;
// Maybe we don't want this when succeeding.
result.feedback = `${MESSAGES.successTest(challenge)}<pre>${stdout}</pre>`;
} catch (e) {
console.error("Test failed", JSON.stringify(e), "\n");

result.success = false;
// ToDo. Parse this and gives a better feedback.
result.feedback = `${MESSAGES.failedTest(challenge)}<pre>${e.stdout}\n\n${
e.stderr
}</pre>`;
}

// Delete files. Don't need to await.
exec(`rm ${challenge.name}/packages/hardhat/contracts/${address}.sol`);

return result;
};

const downloadAndTestContract = async (challengeId, network, address) => {
if (!challenges[challengeId]) {
throw new Error(`Challenge "${challengeId}" not found.`);
}

if (!allowedNetworks.includes(network)) {
throw new Error(`"${network}" is not a valid testnet.`);
}

console.log(`📡 Downloading contract from ${network}`);

try {
await copyContractFromEtherscan(network, address, challengeId);
} catch (e) {
throw e;
}

const challenge = challenges[challengeId];
return await testChallenge({ challenge, network, address });
};

module.exports = {
copyContractFromEtherscan,
allowedNetworks,
downloadAndTestContract,
};
24 changes: 24 additions & 0 deletions utils/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const MESSAGES = {
telegramHelp: (challenge) => {
return (
`<p>After fixing the issues please resubmit the challenge. ` +
`If you are still having issues, join the challenge <a href="${challenge.telegram}" target="_blank">Telegram channel</a>.</p>`
);
},
failedTest: (challenge) => {
return (
`<p>This submission did not pass all tests. Review the output below to see which tests failed and why. ` +
`Viewing the file that is used for testing (packages/hardhat/test/challenge_${challenge.id}.js) ` +
`may help you find the exact section in which the tests failed.</p>` +
`${MESSAGES.telegramHelp(challenge)}<p>--</p>`
);
},
successTest: (challenge) => {
return (
challenge.successMessage ??
`<p>You passed all tests on Challenge ${challenge.id}, keep it up!</p><p>--</p>`
);
},
};

module.exports = { MESSAGES };

0 comments on commit e8d69e9

Please sign in to comment.