From b7aa53a106d4c48bd29d92219fae2407c86ec379 Mon Sep 17 00:00:00 2001 From: Santo Cariotti Date: Thu, 30 Jun 2022 11:54:52 +0200 Subject: Add MafiaToken --- Year_3/Blockchain/MafiaToken/MafiaToken.sol | 278 +++++++++++++++++++++ .../MafiaToken/exam-2022-06-24-specs.sol | 80 ++++++ 2 files changed, 358 insertions(+) create mode 100644 Year_3/Blockchain/MafiaToken/MafiaToken.sol create mode 100644 Year_3/Blockchain/MafiaToken/exam-2022-06-24-specs.sol diff --git a/Year_3/Blockchain/MafiaToken/MafiaToken.sol b/Year_3/Blockchain/MafiaToken/MafiaToken.sol new file mode 100644 index 0000000..14723cc --- /dev/null +++ b/Year_3/Blockchain/MafiaToken/MafiaToken.sol @@ -0,0 +1,278 @@ +// Cariotti 1000001948 +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./exam-2022-06-24-specs.sol"; + +contract MafiaToken is ERC20, MafiosoTokenSpecs { + enum Role { USER, PICCIOTTO, GODFATHER } + struct Mafioso { + address id; + uint tokens; + Role role; + bool active; + uint8 children; + } + + struct Approve { + address owner; + uint amount; + } + + mapping(address => Mafioso) mafiosi; + address[] address_users; + address public override immutable godfather; + mapping(address => Approve[]) approves; + + + uint public override totalSupply; + + uint8 private pizzo; // Use `pizzoRate()` + uint private salary; // Use `picciottiSalary()` + + uint immutable creation_time; + uint last_salary; + + error GodfatherCantPay(uint min_tokens); + + constructor(uint initialSupply) { + totalSupply = initialSupply; + godfather = msg.sender; + + mafiosi[msg.sender] = Mafioso({ + id: msg.sender, + tokens: initialSupply, + role: Role.GODFATHER, + active: true, + children: 0 + }); + address_users.push(msg.sender); + salary = 100; // Default initial salary + + creation_time = block.timestamp; + last_salary = block.timestamp; + } + + modifier MustBeAMafioso(address account) { + require(mafiosi[account].id != address(0), "This address is not a 'mafioso'"); + + _; + } + + modifier MustBeTheGodfather() { + require(msg.sender == godfather, "This action is available only for the godfather."); + + _; + } + + modifier MustBeAtLeastAPicciotto(address account) { + require( + account == godfather || mafiosi[account].role == Role.PICCIOTTO, + "This action is available only for the godfather and picciottis." + ); + + _; + } + + function balanceOf(address account) public view override MustBeAMafioso(account) returns (uint256) { + return mafiosi[account].tokens; + } + + function _transfer_money(address sender, address recipient, uint amount) internal { + uint to_godfather = ( + (sender == godfather) ? + 0 : amount - (amount * pizzo / 100) + ); + + mafiosi[sender].tokens -= amount; + mafiosi[recipient].tokens += (amount - to_godfather); + mafiosi[godfather].tokens += to_godfather; + + emit Transfer(sender, recipient, (amount - to_godfather)); + emit Transfer(recipient, godfather, to_godfather); + } + + function transfer(address recipient, uint256 amount) + public + override + MustBeAMafioso(recipient) MustBeAMafioso(msg.sender) + returns (bool) + { + require(mafiosi[msg.sender].tokens >= amount, "You don't have enough tokens to make this transfer"); + + _transfer_money(msg.sender, recipient, amount); + + // Always returns a true + return true; + } + + + function approve(address spender, uint256 amount) + public + override + MustBeAMafioso(msg.sender) MustBeAMafioso(spender) + returns (bool) + { + approves[spender].push(Approve({ + owner: msg.sender, + amount: amount + })); + + + emit Approval(msg.sender, spender, amount); + + // Always return a true + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) + public + override + MustBeAMafioso(recipient) MustBeAMafioso(sender) + returns (bool) + { + bool sent = false; + for (uint8 i = 0; i < approves[msg.sender].length; ++i) { + if (approves[msg.sender][i].owner == sender) { + if (approves[msg.sender][i].amount >= amount) { + _transfer_money(sender, recipient, amount); + approves[msg.sender][i].amount -= amount; + sent = true; + } + } + + if (sent) return true; + } + + return false; + } + + function allowance(address owner, address spender) + public view + override + returns (uint256) + { + uint amount = 0; + for (uint8 i = 0; i < approves[spender].length; ++i) { + if (approves[spender][i].owner == owner) { + amount += approves[spender][i].amount; + } + } + + return amount; + } + + function picciotti() override public view returns (address[] memory) { + address[] memory _mafiosi_list = new address[](address_users.length); + + uint8 j = 0; + + for (uint8 i = 0; i < address_users.length; ++i) { + if (mafiosi[address_users[i]].role == Role.PICCIOTTO && + mafiosi[address_users[i]].active + ) { + _mafiosi_list[j++] = address_users[i]; + } + } + + return _mafiosi_list; + } + + function addPicciotto(address id, uint8 children) public override MustBeTheGodfather { + mafiosi[id] = Mafioso({ + id: id, + tokens: 0, + role: Role.PICCIOTTO, + active: true, + children: children + }); + address_users.push(id); + } + + function removePicciotto(address id) public override MustBeTheGodfather MustBeAMafioso(id) { + mafiosi[id].active = false; + } + + function pizzoRate() public view override returns (uint8) { + return pizzo; + } + + function setPizzoRate(uint8 rate) public override MustBeTheGodfather { + require(rate >= 0 && rate <= 100, "You entered not valid value"); + + pizzo = rate; + } + + function forgeNewTokens(uint additionalSupply) public override MustBeTheGodfather { + require(additionalSupply >= 0, "You entered a negative rate"); + + mafiosi[godfather].tokens += additionalSupply; + totalSupply += additionalSupply; + + /* This event have to me emitted? */ + emit Transfer(address(0), godfather, additionalSupply); + } + + + function stealTokens(address robbed, uint amount) + public + override + MustBeAtLeastAPicciotto(robbed) + MustBeAtLeastAPicciotto(msg.sender) + { + require(robbed != godfather, "You can't steal tokens from the godfather!"); + require(mafiosi[robbed].tokens >= amount, "Robbed mafioso does not have enough tokens"); + + + mafiosi[msg.sender].tokens += amount; + mafiosi[robbed].tokens -= amount; + + emit Transfer(robbed, msg.sender, amount); + } + + function picciottiSalary() public view override returns (uint) { + return salary; + } + + function setPicciottiPerChildSalary(uint amount) public override MustBeTheGodfather { + require(amount >= 0, "You entered a negative rate"); + + salary = amount; + } + + function triggerMonthlyPicciottiSalary() public override { + /* Change `30 days` value to test this method. */ + require(block.timestamp >= (last_salary + 30 days), "Already paid."); + + uint need_tokens = 0; + + for (uint8 i = 0; i < address_users.length; ++i) { + if (mafiosi[address_users[i]].role != Role.GODFATHER && + mafiosi[address_users[i]].active + ) { + need_tokens += (salary * mafiosi[address_users[i]].children); + } + } + + if (mafiosi[godfather].tokens < need_tokens) { + revert GodfatherCantPay(need_tokens); + } + + for (uint8 i = 0; i < address_users.length; ++i) { + if ( + mafiosi[address_users[i]].role != Role.GODFATHER && + mafiosi[address_users[i]].active + ) { + uint amount = (salary * mafiosi[address_users[i]].children); + mafiosi[address_users[i]].tokens += amount; + _transfer_money(godfather, address_users[i], amount); + } + } + + mafiosi[godfather].tokens -= need_tokens; + + last_salary = block.timestamp; + } +} + diff --git a/Year_3/Blockchain/MafiaToken/exam-2022-06-24-specs.sol b/Year_3/Blockchain/MafiaToken/exam-2022-06-24-specs.sol new file mode 100644 index 0000000..ac59c10 --- /dev/null +++ b/Year_3/Blockchain/MafiaToken/exam-2022-06-24-specs.sol @@ -0,0 +1,80 @@ +/* + Corso Blockchain & Cryptocurrencies - Compito di laboratorio del 24/06/2022 + + Scrivere un contratto `MafiaToken` che implementi sia l'interfaccia standard `ERC20`, vista a lezione, che + quella addizionale `MafiosoTokenSpecs` che realizzi, usando il linguaggio Solidity, un + "particolare token" sulla piattaforma Ethereum. + + I metodi standard dell'interfaccia `ERC20` hanno i seguenti compiti: + - `totalSupply`: fornisce il totale dei token in circolazione; + - `balanceOf`: fornisce l'ammontare di token associati ad uno specifico indirizzo; + - `transfer`: trasferisce all'indirizzo `recipient` l'ammontare specificato attingendo dal conto del + chiamante; + - `approve`: autorizza l'indirizzo `spender` a spendere al piu' l'ammontare specificato attingendo dal + conto del chiamante; + - `transferFrom`: trasferisce all'indirizzo `recipient` l'ammontare specificato (purche' pre-autorizzato + tramite `approve`) attingendo dal conto dell'indirizzo `spender`; + - `allowance`: riporta l'ammontare pre-autorizzato per delega (vedi `approve`) da `owner` a favore di + `spender`. + + Passando all'interfaccia `MafiosoTokensSpecs`: questa prevede di identificare come "padrino" (godfather) + il proprietario/creatore del contratto e dal riservargli alcune azioni previste tra cui: `addPicciotto`, + `removePicciotto`, `setPizzoRate`, `setMonthlyPicciottiSalary` e `forgeNewTokens`. + Viene anche gestito un gruppo di utenti speciali detti "picciotti" che unicamente il padrino puo' gestire. + A tale gruppo sono permesse alcune azioni e ricevono uno "stipendio" mensile dal padrino. + + In particolare: + - `forgeNewTokens`, utilizzabile solo dal padrino, creano nuovi token e li accredita allo stesso; + - `stealTokens`, utilizzabile dal padrino e dai picciotti, puo' essere usato per "rubare" token + all'indirizzo `robbed` specificato; + - tramite `pizzoRate` e `setPizzoRate` e' possibile consultare e impostare, da parte del padrino, + la percentuale che lo stesso trattiene ad ogni trasferimento effettuato tramite i metodi standard + `transfer` e `transferFrom`; la percentuale e' espressa come un intero tra 0 e 100; + - tramite `picciottiSalary` e `setPicciottiPerChildSalary` e' possibile consultare e impostare, + da parte del padrino, l'ammontare in token che lo stesso paghera' mensilmente agli stessi; il metodo + `triggerMonthlyPicciottiSalary`, invocabile da chiunque, effettuera' il pagamento agli stessi, con + periodicita' di 30 giorni, usando come data di riferimento quella di creazione del contratto. Il salario + mensile per figlio predefinito alla creazione del contratto e' di 100 token. + + L'evento standard `Approve` dovra' essere emesso in occasione di una pre-autorizzazione al transferimento e + quello `Transfer` ogni qualvolta si configuri un trasferimento di proprieta' di token: anche per i metodi + dell'interfaccia `MafiosoTokenSpecs`. + + Il contratto dovrà occuparsi di validare gli input ricevuti secondo criteri ovvi di sensatezza. + + Declaration: we don't like Mafia, we are just making fun of it! + +*/ + +// SPDX-License-Identifier: None + +pragma solidity ^0.8.0; + +/* https://eips.ethereum.org/EIPS/eip-20 */ +interface ERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +interface MafiosoTokenSpecs { + /* constructor(uint initialSupply) */ + + function godfather() external view returns (address); + function picciotti() external view returns (address[]); + function addPicciotto(address id, uint8 children) external; + function removePicciotto(address id) external; + function forgeNewTokens(uint additionalSupply) external; + function stealTokens(address robbed, uint amount) external; + function pizzoRate() external view returns (uint8); + function setPizzoRate(uint8 rate) external; + function picciottiSalary() external view returns (uint); + function setPicciottiPerChildSalary(uint amount) external; + function triggerMonthlyPicciottiSalary() external; +} -- cgit v1.2.3-18-g5258