├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── contracts ├── Migrations.sol └── SimpleBank.sol ├── README.md ├── test └── simpleBank.test.js └── truffle-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const ether = 10**18; // 1 ether = 1000000000000000000 wei 2 | 3 | var SimpleBank = artifacts.require("SimpleBank"); 4 | 5 | module.exports = function(deployer) { 6 | deployer.deploy(SimpleBank, { value: 30 * ether }); 7 | }; 8 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Bank 2 | 3 | This is a modified version of the Simple Bank smart contract example using Solidity. 4 | Instead of rewarding all clients, which means that the bank contract should hold all that Ether beforehand, 5 | it only rewards the 3 first clients with 10 Ether each. 6 | 7 | Consequently, when deployed, the contract should be fetched 30 Ether and the constructor is payable. 8 | To do so for tests, the Truffle deployment script "2_deploy_contracts.js" is: 9 | 10 | ``` 11 | const ether = 10**18; // 1 ether = 1000000000000000000 wei 12 | var SimpleBank = artifacts.require("SimpleBank"); 13 | 14 | module.exports = function(deployer) { 15 | deployer.deploy(SimpleBank, { value: 30 * ether }); 16 | }; 17 | ``` 18 | 19 | The contract features an additional method to retrieve all the Ether in the contract. 20 | 21 | ## Requirements 22 | 23 | Contract deployment and testing is done via [Truffle](https://truffleframework.com/). To install Truffle: 24 | 25 | ``` 26 | npm install -g truffle 27 | ``` 28 | 29 | Note: checked with version 30 | 31 | * Truffle v5.0.37 / Solidity v0.5.8 32 | 33 | ## Deployment and Testing 34 | 35 | First, start a local development network: 36 | 37 | ``` 38 | truffle develop 39 | ``` 40 | 41 | This will start Truffle Develop at http://127.0.0.1:9545 together with 10 sample accounts. 42 | 43 | Then, compile the contracts in a different terminal (truffle develop keeps running the network): 44 | 45 | ``` 46 | truffle compile 47 | ``` 48 | 49 | If there are no errors, the contracts can be deployed to the local development network: 50 | 51 | ``` 52 | truffle migrate 53 | ``` 54 | 55 | Finally, they can be tested: 56 | 57 | ``` 58 | truffle test 59 | ``` 60 | 61 | With the following expected output: 62 | 63 | ``` 64 | Using network 'development'. 65 | 66 | Contract: SimpleBank - basic initialization 67 | ✓ should reward 3 first clients with 1 balance (168ms) 68 | ✓ should deposit correct amount (63ms) 69 | 70 | Contract: SimpleBank - proper withdrawal 71 | ✓ should withdraw correct amount (63ms) 72 | 73 | Contract: SimpleBank - incorrect withdrawal 74 | ✓ should keep balance unchanged if withdraw greater than balance (73ms) 75 | 76 | Contract: SimpleBank - fallback works 77 | ✓ should revert ether sent to this contract through fallback 78 | 79 | 80 | 5 passing (482ms) 81 | ``` 82 | -------------------------------------------------------------------------------- /contracts/SimpleBank.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.8; 2 | 3 | contract SimpleBank { 4 | uint8 private clientCount; 5 | mapping (address => uint) private balances; 6 | address public owner; 7 | 8 | // Log the event about a deposit being made by an address and its amount 9 | event LogDepositMade(address indexed accountAddress, uint amount); 10 | 11 | // Constructor is "payable" so it can receive the initial funding of 30, 12 | // required to reward the first 3 clients 13 | constructor() public payable { 14 | require(msg.value == 30 ether, "30 ether initial funding required"); 15 | /* Set the owner to the creator of this contract */ 16 | owner = msg.sender; 17 | clientCount = 0; 18 | } 19 | 20 | /// @notice Enroll a customer with the bank, 21 | /// giving the first 3 of them 10 ether as reward 22 | /// @return The balance of the user after enrolling 23 | function enroll() public returns (uint) { 24 | if (clientCount < 3) { 25 | clientCount++; 26 | balances[msg.sender] = 10 ether; 27 | } 28 | return balances[msg.sender]; 29 | } 30 | 31 | /// @notice Deposit ether into bank, requires method is "payable" 32 | /// @return The balance of the user after the deposit is made 33 | function deposit() public payable returns (uint) { 34 | balances[msg.sender] += msg.value; 35 | emit LogDepositMade(msg.sender, msg.value); 36 | return balances[msg.sender]; 37 | } 38 | 39 | /// @notice Withdraw ether from bank 40 | /// @return The balance remaining for the user 41 | function withdraw(uint withdrawAmount) public returns (uint remainingBal) { 42 | // Check enough balance available, otherwise just return balance 43 | if (withdrawAmount <= balances[msg.sender]) { 44 | balances[msg.sender] -= withdrawAmount; 45 | msg.sender.transfer(withdrawAmount); 46 | } 47 | return balances[msg.sender]; 48 | } 49 | 50 | /// @notice Just reads balance of the account requesting, so "constant" 51 | /// @return The balance of the user 52 | function balance() public view returns (uint) { 53 | return balances[msg.sender]; 54 | } 55 | 56 | /// @return The balance of the Simple Bank contract 57 | function depositsBalance() public view returns (uint) { 58 | return address(this).balance; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/simpleBank.test.js: -------------------------------------------------------------------------------- 1 | var SimpleBank = artifacts.require("./SimpleBank.sol"); 2 | 3 | const ether = 10**18; // 1 ether = 1000000000000000000 wei 4 | const reward = 10 * ether; 5 | const initialDepositsBalance = 30 * ether; 6 | 7 | contract("SimpleBank - basic initialization", function(accounts) { 8 | const alice = accounts[1]; 9 | const bob = accounts[2]; 10 | const charlie = accounts[3]; 11 | const dave = accounts[4]; 12 | 13 | it("should reward 3 first clients with 10 balance", async () => { 14 | const bank = await SimpleBank.deployed(); 15 | 16 | await bank.enroll({from: alice}); 17 | const aliceBalance = await bank.balance({from: alice}); 18 | assert.equal(aliceBalance, reward, "initial balance is incorrect"); 19 | 20 | await bank.enroll({from: bob}); 21 | const bobBalance = await bank.balance({from: bob}); 22 | assert.equal(bobBalance, reward, "initial balance is incorrect"); 23 | 24 | await bank.enroll({from: charlie}); 25 | const charlieBalance = await bank.balance({from: charlie}); 26 | assert.equal(charlieBalance, reward, "initial balance is incorrect"); 27 | 28 | await bank.enroll({from: dave}); 29 | const daveBalance = await bank.balance({from: dave}); 30 | assert.equal(daveBalance, 0, "initial balance is incorrect"); 31 | 32 | const depositsBalance = await bank.depositsBalance(); 33 | assert.equal(depositsBalance, initialDepositsBalance, "initial balance is incorrect"); 34 | }); 35 | 36 | it("should deposit correct amount", async () => { 37 | const bank = await SimpleBank.deployed(); 38 | const deposit = 2 * ether; 39 | 40 | const receipt = await bank.deposit({from: alice, value: web3.utils.toBN(deposit)}); 41 | 42 | const balance = await bank.balance({from: alice}); 43 | assert.equal(balance, reward + deposit, 44 | "deposit amount incorrect, check deposit method"); 45 | const depositsBalance = await bank.depositsBalance(); 46 | assert.equal(depositsBalance, initialDepositsBalance + deposit, 47 | "bank deposits balance should be increased"); 48 | 49 | const expectedEventResult = {accountAddress: alice, amount: deposit}; 50 | assert.equal(receipt.logs[0].args.accountAddress, expectedEventResult.accountAddress, 51 | "LogDepositMade event accountAddress property not emitted"); 52 | assert.equal(receipt.logs[0].args.amount, expectedEventResult.amount, 53 | "LogDepositMade event amount property not emitted"); 54 | }); 55 | }); 56 | 57 | contract("SimpleBank - proper withdrawal", function(accounts) { 58 | const alice = accounts[1]; 59 | 60 | it("should withdraw correct amount", async () => { 61 | const bank = await SimpleBank.deployed(); 62 | const deposit = 5 * ether; 63 | 64 | await bank.deposit({from: alice, value: web3.utils.toBN(deposit)}); 65 | await bank.withdraw(web3.utils.toBN(deposit), {from: alice}); 66 | 67 | const balance = await bank.balance({from: alice}); 68 | assert.equal(balance, deposit - deposit, "withdraw amount incorrect"); 69 | }); 70 | }); 71 | 72 | contract("SimpleBank - incorrect withdrawal", function(accounts) { 73 | const alice = accounts[1]; 74 | 75 | it("should keep balance unchanged if withdraw greater than balance", async() => { 76 | const bank = await SimpleBank.deployed(); 77 | const deposit = 3 * ether; 78 | 79 | await bank.deposit({from: alice, value: web3.utils.toBN(deposit)}); 80 | await bank.withdraw(web3.utils.toBN(deposit + 1*ether), {from: alice}); 81 | 82 | const balance = await bank.balance({from: alice}); 83 | assert.equal(balance, deposit, "balance should be kept intact"); 84 | }); 85 | }); 86 | 87 | contract("SimpleBank - fallback works", function(accounts) { 88 | const alice = accounts[1]; 89 | 90 | it("should revert ether sent to this contract through fallback", async() => { 91 | const bank = await SimpleBank.deployed(); 92 | const deposit = 3 * ether; 93 | 94 | try { 95 | await bank.send(web3.utils.toBN(deposit), {from: alice}); 96 | } catch(e) { 97 | assert(e, "Error: VM Exception while processing transaction: revert"); 98 | } 99 | 100 | const depositsBalance = await bank.depositsBalance(); 101 | assert.equal(depositsBalance, initialDepositsBalance, "balance should be kept intact"); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | development: { 46 | host: "127.0.0.1", // Localhost (default: none) 47 | port: 9545, // Standard Ethereum port (default: none) 48 | network_id: "*", // Any network (default: none) 49 | }, 50 | 51 | // Another network with more advanced options... 52 | // advanced: { 53 | // port: 8777, // Custom port 54 | // network_id: 1342, // Custom network 55 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 56 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 57 | // from:
, // Account to send txs from (default: accounts[0]) 58 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 59 | // }, 60 | 61 | // Useful for deploying to a public network. 62 | // NB: It's important to wrap the provider as a function. 63 | // ropsten: { 64 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 65 | // network_id: 3, // Ropsten's id 66 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 67 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 68 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 69 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 70 | // }, 71 | 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | // timeout: 100000 83 | }, 84 | 85 | // Configure your compilers 86 | compilers: { 87 | // solc: { 88 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 89 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 90 | // settings: { // See the solidity docs for advice about optimization and evmVersion 91 | // optimizer: { 92 | // enabled: false, 93 | // runs: 200 94 | // }, 95 | // evmVersion: "byzantium" 96 | // } 97 | // } 98 | } 99 | } 100 | --------------------------------------------------------------------------------