Files
lab2/exam/index.html
2026-02-26 16:17:30 +01:00

425 lines
16 KiB
HTML

<!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>