Lab 2
This commit is contained in:
14
exam/common.js
Normal file
14
exam/common.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
export function installDeps() {
|
||||
// check if node_modules exists
|
||||
if (fs.existsSync('./node_modules')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// install dependencies
|
||||
console.log('Installing dependencies...');
|
||||
execSync('npm install', { stdio: 'inherit' });
|
||||
console.log('Dependencies installed');
|
||||
}
|
||||
54
exam/exam.js
Normal file
54
exam/exam.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { installDeps } from './common.js';
|
||||
|
||||
const SERVER_PORT = 3018;
|
||||
|
||||
async function main() {
|
||||
installDeps();
|
||||
|
||||
const parentDirectory = process.cwd();
|
||||
const indexHtml = fs.readFileSync(path.join(parentDirectory, 'exam', 'index.html'), 'utf8');
|
||||
const examTheory = fs.readFileSync(path.join(parentDirectory, 'exam', 'theory.json'), 'utf8');
|
||||
let examTheoryData = JSON.parse(examTheory);
|
||||
|
||||
// import express and start the server
|
||||
const express = await import('express');
|
||||
const app = express.default();
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
res.send(indexHtml);
|
||||
});
|
||||
|
||||
app.get('/theory', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.json(examTheoryData);
|
||||
});
|
||||
|
||||
app.post('/theory-answer', (req, res) => {
|
||||
const questionId = req.body.questionId;
|
||||
const answer = req.body.answer;
|
||||
const index = examTheoryData.findIndex(q => q.id === questionId);
|
||||
if (index !== -1) {
|
||||
examTheoryData[index].answer = answer;
|
||||
fs.writeFileSync(path.join(parentDirectory, 'exam', 'theory.json'), JSON.stringify(examTheoryData, null, 2));
|
||||
res.status(200).json({ success: true });
|
||||
} else {
|
||||
res.status(404).json({ success: false, error: 'Question not found' });
|
||||
}
|
||||
});
|
||||
|
||||
const open = await import('open');
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log(`Server is running on port ${SERVER_PORT}`);
|
||||
open.default(`http://localhost:${SERVER_PORT}/`);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
424
exam/index.html
Normal file
424
exam/index.html
Normal file
@@ -0,0 +1,424 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
||||
>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
|
||||
>
|
||||
<style>
|
||||
[data-theme="dark"] {
|
||||
--pico-background-color: #282a36; /* Custom color */
|
||||
}
|
||||
|
||||
.app-header {
|
||||
border-bottom: 1px solid var(--pico-muted-border-color);
|
||||
box-shadow: 0 1px 0 var(--pico-muted-border-color);
|
||||
}
|
||||
|
||||
[data-theme="light"] .app-header {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .app-header {
|
||||
background-color: rgba(9, 21, 21, 0.6);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .sidebar {
|
||||
border-right: 3px solid rgba(9, 21, 21, 0.6);
|
||||
}
|
||||
|
||||
.app-header nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.app-header hgroup {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-header #theme-icon {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.app-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.app-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sidebar {
|
||||
width: 315px;
|
||||
flex-shrink: 0;
|
||||
padding: 1.5rem;
|
||||
border-right: 1px solid var(--pico-muted-border-color);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.sidebar nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.sidebar .sidebar-button {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem 0.75rem 0;
|
||||
border-bottom: 1px solid var(--pico-muted-border-color);
|
||||
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] hgroup>:not(:first-child):last-child {
|
||||
--pico-color: revert;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.main-content section.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-content section p.mt-3 {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.main-content section .theory-question:first-of-type {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.main-content section .theory-question {
|
||||
border-top: 1px solid var(--pico-muted-border-color);
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.main-content section .theory-question p.question {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.main-content section hgroup p {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* Mobile: hide sidebar, show desktop-only message */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
.main-content {
|
||||
display: none;
|
||||
}
|
||||
.mobile-message {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
.mobile-message {
|
||||
display: none;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.mobile-message article {
|
||||
max-width: 28rem;
|
||||
}
|
||||
</style>
|
||||
<title>Lab 2</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root">
|
||||
<header class="app-header">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<hgroup>
|
||||
<h2>Lab 2</h2>
|
||||
<p>Distributed Systems and Blockchain</p>
|
||||
</hgroup>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<details class="dropdown">
|
||||
<summary>
|
||||
Documentation
|
||||
</summary>
|
||||
<ul>
|
||||
<li><a href="https://api.next.code-camp.org/cdn/viem-docs.pdf" target="_blank">Viem</a></li>
|
||||
<li><a href="https://api.next.code-camp.org/cdn/solidity-docs.pdf" target="_blank">Solidity</a></li>
|
||||
<li><a href="https://api.next.code-camp.org/cdn/crowdfunding-contract.html" target="_blank">Contract example</a></li>
|
||||
<li><a href="https://api.next.code-camp.org/cdn/crowdfunding-tests.html" target="_blank">Tests example</a></li>
|
||||
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span id="theme-icon" aria-hidden="true" onclick="cycleTheme()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32" fill="currentColor" class="icon-theme-toggle "><clipPath id="theme-toggle-cutout"><path d="M0-11h25a1 1 0 0017 13v30H0Z"></path></clipPath><g clip-path="url(#theme-toggle-cutout)"><circle cx="16" cy="16" r="8.4"></circle><path d="M18.3 3.2c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3S14.7.9 16 .9s2.3 1 2.3 2.3zm-4.6 25.6c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3-1 2.3-2.3 2.3-2.3-1-2.3-2.3zm15.1-10.5c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zM3.2 13.7c1.3 0 2.3 1 2.3 2.3s-1 2.3-2.3 2.3S.9 17.3.9 16s1-2.3 2.3-2.3zm5.8-7C9 7.9 7.9 9 6.7 9S4.4 8 4.4 6.7s1-2.3 2.3-2.3S9 5.4 9 6.7zm16.3 21c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zm2.4-21c0 1.3-1 2.3-2.3 2.3S23 7.9 23 6.7s1-2.3 2.3-2.3 2.4 1 2.4 2.3zM6.7 23C8 23 9 24 9 25.3s-1 2.3-2.3 2.3-2.3-1-2.3-2.3 1-2.3 2.3-2.3z"></path></g></svg>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="app-body">
|
||||
<aside class="sidebar">
|
||||
<nav>
|
||||
<a href="#theory" class="sidebar-button">Theory</a>
|
||||
<a href="#viem" class="sidebar-button">Coding - Viem</a>
|
||||
<a href="#hardhat" class="sidebar-button">Coding - Hardhat</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content container" id="main-content">
|
||||
<section id="theory">
|
||||
<hgroup>
|
||||
<h2>Theory</h1>
|
||||
<p>Answer the following questions to the best of your ability. Each question has exactly one correct answer. There are no negative marks for incorrect answers.</p>
|
||||
</hgroup>
|
||||
|
||||
|
||||
<div id="theory-questions"></div>
|
||||
</section>
|
||||
|
||||
|
||||
<section id="viem" class="hidden">
|
||||
<h2>Coding - Viem: Contract Explorer</h1>
|
||||
|
||||
<p>In the repository you downloaded, there is a directory called <strong>/scripts</strong>. Inside, there is a file called <strong>contract-explorer.ts</strong>, which is used to analyze an ERC-20 contract (for fungible tokens) on the Next Testnet network, and to get the balance of a specific Ethereum address within that contract. Your job is to complete the script so that it prints the following information for the contract:</p>
|
||||
|
||||
<ul>
|
||||
<li>The name</li>
|
||||
<li>The symbol</li>
|
||||
<li>The decimals (for example, for PAXG it's 18 decimals, like Wei and Ether)</li>
|
||||
<li>The balance (in that contract), for a specific address</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-3">You can test the script by running the following command:</p>
|
||||
<pre><code>npx tsx scripts/contract-explorer.ts</code></pre>
|
||||
|
||||
<p class="mt-3">To understand what options are available for ERC-20 tokens, you should start by clicking the "Read contract" tab here: <a href="https://etherscan.code-camp.org/address/0x5FbDB2315678afecb367f032d93F642f64180aa3" target="_blank">view contract</a>.</p>
|
||||
|
||||
<p class="mt-3">For testing, you can use the following information:</p>
|
||||
<div class="overflow-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="word-break: break-all;">Contract address</td>
|
||||
<td>0x5FbDB2315678afecb367f032d93F642f64180aa3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="word-break: break-all;">Account address</td>
|
||||
<td>0xa24d14702cef666131dd8EFC0670318CDCA4Baf3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="mt-3">Hint: you only need to use the readErc20Contract function, which is already provided for you in the starter template.</p>
|
||||
</section>
|
||||
|
||||
|
||||
<section id="hardhat" class="hidden">
|
||||
<h2>Coding - Hardhat: Gradebook</h1>
|
||||
|
||||
<p>In the repository you downloaded, there is a directory called <strong>/contracts</strong> (for storing smart contracts), and a directory called <strong>/test</strong> (for test files). In the first directory, there is a file called <strong>Gradebook.sol</strong> (where you need to write a smart contract in Solidity); while in the second directory, there is a file called <strong>Gradebook.ts</strong> (where you need to write tests for the contract).</p>
|
||||
<p>The contract is meant to be used by a teacher as a public Gradebook, for storing points and calculating grades for students. It should support 3 functions:</p>
|
||||
|
||||
<ul>
|
||||
<li>addStudent(addr, name) - for adding a new student (with their Ethereum address and name).</li>
|
||||
<li>submitScore(addr, points) - for storing the number of points earned by the student (a number between 0 and 100). Points can only be set once for a student and can't be overridden. An event should be fired with the address and the number of points.</li>
|
||||
<li>getGrade(addr) - a view function that calculates a grade based on the points and returns a number between 5 and 10.</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-3">The contract should validate the inputs, and return errors if something is not correct. For calculating a grade based on the number of points, you can use the standard scoring system used at Brainster Next (91-100 is a 10, 81-90 is a 9, ..., and anything below 51 is a 5).</p>
|
||||
|
||||
<p class="mt-3">You can build the contracts by running the following command:</p>
|
||||
<pre><code>npx hardhat build</code></pre>
|
||||
|
||||
<p class="mt-3">After you code the contract, you can start adding tests in the test/Gradebook.ts file (it already has a list of tests you need to add). To run them, you can use the following command:</p>
|
||||
<pre><code>npx hardhat test</code></pre>
|
||||
|
||||
<p class="mt-3">Hint: you have an example contract and tests (see Documentation links on the top) to help you get started.</p>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="mobile-message">
|
||||
<article>
|
||||
<header>
|
||||
<strong>Desktop required</strong>
|
||||
</header>
|
||||
<p>This exam layout needs a larger screen. Please open it on a desktop computer.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function setupTheme() {
|
||||
var THEME_KEY = 'dsb-exam-theme';
|
||||
var html = document.documentElement;
|
||||
var icon = document.getElementById('theme-icon');
|
||||
|
||||
function applyTheme(theme) {
|
||||
html.setAttribute('data-theme', theme);
|
||||
try { localStorage.setItem(THEME_KEY, theme); } catch (e) {}
|
||||
}
|
||||
|
||||
function cycleTheme(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(next);
|
||||
}
|
||||
|
||||
try {
|
||||
var saved = localStorage.getItem(THEME_KEY);
|
||||
if (saved === 'light' || saved === 'dark') applyTheme(saved);
|
||||
} catch (e) {}
|
||||
|
||||
if (icon) icon.addEventListener('click', cycleTheme);
|
||||
}
|
||||
|
||||
function setupSidebar() {
|
||||
var sidebar = document.querySelector('.sidebar');
|
||||
var sidebarButtons = sidebar.querySelectorAll('.sidebar-button');
|
||||
sidebarButtons.forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
var target = button.getAttribute('href');
|
||||
var targetSection = document.querySelector(target);
|
||||
|
||||
var sections = document.querySelectorAll('.main-content section');
|
||||
|
||||
if (targetSection) {
|
||||
document.body.style.opacity = 0;
|
||||
sections.forEach(function(section) {
|
||||
section.classList.add('hidden');
|
||||
});
|
||||
|
||||
targetSection.classList.remove('hidden');
|
||||
setTimeout(function() {
|
||||
window.scrollTo(0, 0);
|
||||
document.body.style.opacity = 1;
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupTheory() {
|
||||
|
||||
function setupTheoryCheckboxes() {
|
||||
var theoryCheckboxes = document.querySelectorAll('.theory-question input[type="checkbox"]');
|
||||
theoryCheckboxes.forEach(function(checkbox) {
|
||||
checkbox.addEventListener('change', function() {
|
||||
var question = checkbox.closest('.theory-question');
|
||||
var questionId = question.getAttribute('id').split('-')[1];
|
||||
var answer = checkbox.getAttribute('name').split('-')[2];
|
||||
var otherCheckboxes = question.querySelectorAll('input[type="checkbox"]');
|
||||
otherCheckboxes.forEach(function(otherCheckbox) {
|
||||
const otherAnswer = otherCheckbox.getAttribute('name').split('-')[2];
|
||||
if (otherAnswer !== answer) {
|
||||
otherCheckbox.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
checkbox.checked = true;
|
||||
fetch('/theory-answer', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
questionId: Number(questionId),
|
||||
answer: answer
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
if (!response.ok) {
|
||||
alert('Failed to submit answer.');
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
alert('Failed to submit answer.');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
fetch('/theory')
|
||||
.then(response => response.json())
|
||||
.then(function(data) {
|
||||
var theory = document.querySelector('#theory');
|
||||
|
||||
var html = '';
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var questionId = data[i].id;
|
||||
var question = data[i].question;
|
||||
var options = data[i].options;
|
||||
var answer = data[i].answer;
|
||||
|
||||
html += '<div class="theory-question" id="question-' + questionId + '">';
|
||||
html += '<p class="question">' + (i + 1) + '. ' + question + "</p>";
|
||||
const sortedOptionKeys = Object.keys(options).sort();
|
||||
|
||||
html += '<fieldset>';
|
||||
sortedOptionKeys.forEach(function(optionKey) {
|
||||
html += '<label>';
|
||||
html += '<input type="checkbox" name="question-' + questionId + '-' + optionKey + '"' + (answer === optionKey ? ' checked' : '') +'>';
|
||||
html += ' ' + options[optionKey];
|
||||
html += '</label>';
|
||||
});
|
||||
html += '</fieldset>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
document.querySelector('#theory-questions').innerHTML = html;
|
||||
setupTheoryCheckboxes();
|
||||
});
|
||||
}
|
||||
|
||||
setupTheme();
|
||||
setupSidebar();
|
||||
setupTheory();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
122
exam/theory.json
Normal file
122
exam/theory.json
Normal file
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"question": "Which system has a single point of failure?",
|
||||
"options": {
|
||||
"A": "Monolithic system",
|
||||
"B": "Peer-to-peer network",
|
||||
"C": "Distributed system",
|
||||
"D": "Content Delivery Network"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"question": "Vertical scaling is limited because:",
|
||||
"options": {
|
||||
"A": "Networks are unreliable",
|
||||
"B": "It depends on consensus",
|
||||
"C": "A single machine has a maximum capacity",
|
||||
"D": "It requires multiple data centers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"question": "Replication helps with:",
|
||||
"options": {
|
||||
"A": "Increasing latency",
|
||||
"B": "Fault tolerance",
|
||||
"C": "Reducing decentralization",
|
||||
"D": "Removing consensus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"question": "RPC aims to:",
|
||||
"options": {
|
||||
"A": "Replace HTTP",
|
||||
"B": "Make remote calls look like local function calls",
|
||||
"C": "Store messages in queues",
|
||||
"D": "Encrypt all communication"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"question": "A producer in a messaging queue:",
|
||||
"options": {
|
||||
"A": "Processes messages",
|
||||
"B": "Stores blockchain blocks",
|
||||
"C": "Sends messages to a broker",
|
||||
"D": "Subscribes to topics"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"question": "REST is:",
|
||||
"options": {
|
||||
"A": "A binary serialization format",
|
||||
"B": "An architectural style using HTTP methods on resources",
|
||||
"C": "A consensus mechanism",
|
||||
"D": "A blockchain protocol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"question": "In Ethereum, if two conflicting transactions attempt a double spend:",
|
||||
"options": {
|
||||
"A": "Both are accepted",
|
||||
"B": "Neither is accepted",
|
||||
"C": "A globally accepted order is selected and one is rejected",
|
||||
"D": "The sender asynchronously chooses which one wins"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"question": "Which of the following is required to retrieve (read) the Ether balance of a publicly known Ethereum address?",
|
||||
"options": {
|
||||
"A": "The private key",
|
||||
"B": "The mnemonics for the wallet account",
|
||||
"C": "Both the private key and the mnemonics",
|
||||
"D": "None of the above"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"question": "Ethereum is Proof of Stake. It prioritizes validators based on blockchain rules and:",
|
||||
"options": {
|
||||
"A": "CPU power",
|
||||
"B": "Network latency",
|
||||
"C": "Geographic location",
|
||||
"D": "Amount of cryptocurrency staked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"question": "Solidity is primarily used to:",
|
||||
"options": {
|
||||
"A": "Build REST APIs",
|
||||
"B": "Develop operating systems",
|
||||
"C": "Mine Bitcoin",
|
||||
"D": "Write smart contracts for Ethereum"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"question": "If gas runs out during the execution of a transaction:",
|
||||
"options": {
|
||||
"A": "Execution stops and state changes from the current call revert",
|
||||
"B": "Only part of the smart contract storage updates",
|
||||
"C": "Changes to state are partially applied",
|
||||
"D": "Transaction becomes free"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"question": "Immutability in blockchain means:",
|
||||
"options": {
|
||||
"A": "The balance of an address cannot be updated in subsequent blocks",
|
||||
"B": "Data is deleted after one confirmation by a validator",
|
||||
"C": "Past blocks cannot be altered without changing subsequent blocks and gaining consensus",
|
||||
"D": "Other users cannot read transactions that don't belong to them"
|
||||
}
|
||||
}
|
||||
]
|
||||
62
exam/zip.js
Normal file
62
exam/zip.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { installDeps } from './common.js';
|
||||
import packageJson from '../package.json' with { type: 'json' };
|
||||
|
||||
async function main() {
|
||||
installDeps();
|
||||
|
||||
const prompts = await import('prompts');
|
||||
console.log(`Lets create a zip archive for your submission.`);
|
||||
const { studentId } = await prompts.default({
|
||||
type: 'text',
|
||||
name: 'studentId',
|
||||
message: 'What is your student ID?',
|
||||
format: (value) => value.trim().replace(/[^A-Za-z0-9]/g, ''),
|
||||
});
|
||||
|
||||
if (!studentId) {
|
||||
console.error('Student ID is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const parentDirectory = process.cwd();
|
||||
const archiveName = `${packageJson.name}-${studentId}.zip`;
|
||||
const zipPath = path.join(parentDirectory, archiveName);
|
||||
|
||||
if (fs.existsSync(zipPath)) {
|
||||
console.log(`Removing existing archive at ${zipPath}`);
|
||||
fs.unlinkSync(zipPath);
|
||||
}
|
||||
|
||||
const archiver = await import('archiver');
|
||||
const archive = archiver.default('zip');
|
||||
archive.on('error', (err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
archive.glob('**/*', {
|
||||
cwd: parentDirectory,
|
||||
ignore: [
|
||||
'node_modules/**',
|
||||
'.git/**',
|
||||
'artifacts/**',
|
||||
'cache/**',
|
||||
'.env',
|
||||
'.DS_Store',
|
||||
'*.zip',
|
||||
'exam/common.js',
|
||||
'exam/exam.js',
|
||||
'exam/index.html',
|
||||
'exam/zip.js'
|
||||
]
|
||||
|
||||
});
|
||||
archive.pipe(fs.createWriteStream(zipPath));
|
||||
await archive.finalize();
|
||||
|
||||
console.log();
|
||||
console.log(`Archive created: ${zipPath}`);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user