pragma solidity 0.8.7; // The Basics: contract HelloWorld { // called state variables: string message; // CONSTRUCTORS // https://academy.moralis.io/lessons/constructors-2 // runs only 1x during compile. constructor(string memory _msg){ message = _msg; } // VIEW vs PURE // https://academy.moralis.io/lessons/view-pure // view function, reads data outside of func. function HelloExternalScope() public view returns(string memory) { return message; } // pure function, does NOT read/write data outside of func. function HelloLocalScope() public pure returns(string memory) { // called local variables: string memory localMessage = "Hello Function"; // NOTE ABOUT STRINGS: // + and . don't work to concat strings, nothing // is built-in to solidity for string concats. // return localMessage + message; // return localMessage . message; return localMessage; } // CONTROL FLOW (if statements) // https://academy.moralis.io/lessons/control-flow // NOTE: parameters naming scheme is prepended with "_" to mark is as an "input variable", // but you can name the variable anything you want function HelloIfStatement(int _number) public pure returns(string memory) { // NOTE: these variables are ALWAYS available in every function: // msg.sender // this address executing the code // msg.value // the callers amount of ETH (or currency) // standard if statement if (_number <= 10) { return "good number"; } else { return "bad number"; } } // LOOPS // https://academy.moralis.io/lessons/loops // NOTE: returns(int) doesn't need memory *unlike returns(string memory) // While loops: function HelloWhile(int _number) public pure returns(int) { int i = 0; while(i < 10){ // NOTE: no way to console.log(); _number++; i++; } return _number; } // For loops: function HelloFor(int _number) public pure returns(int) { for(int i = 0; i < 10; i++){ // NOTE: no way to console.log(); _number++; } return _number; } // GETTERS & SETTERS // https://academy.moralis.io/lessons/setter-functions int num; // Get function GetNum() public view returns(int){ return num; } // Set function SetNum(int _n) public returns(int){ num = _n; return num; } // ARRAYS // https://academy.moralis.io/lessons/arrays int[] numbersDynamic; // dynamic length array int[3] numbersStatic; // static length array of 3 // int[3] numbersStaticVals = [1,2,3]; // static length with init vals function AppendDynamicNumbers(int _n) public returns(int[] memory){ // only dynamic arrays have the ".push()" method. numbersDynamic.push(_n); return numbersDynamic; } function GetDynamicNumbersID(uint _id) public view returns(int){ // NOTE: if _id is passed that doesn't exist, an error is triggered: // call to HelloWorld.GetDynamicNumbersID errored: VM error: revert. // revert // The transaction has been reverted to the initial state. // Note: The called function should be payable if you send value and the value you send should be less than your current balance. // Debug the transaction to get more information return numbersDynamic[_id]; } // NOTE: _id needs to be uint type, since no position is negative. function AppendStaticNumbers(uint _id, int _n) public returns(int[3] memory){ // static length arrays don't have ".push()", // you need to specify the position: numbersStatic[_id] = _n; return numbersStatic; } // STRUCTS // https://academy.moralis.io/lessons/structs-3 struct Person{ uint age; string name; } Person[] people; function AddPerson(uint _age, string memory _name) public { Person memory p = Person(_age, _name); people.push(p); // This also works // people.push(Person(_age, _name)); } // NOTE: Filip says you can't return "Person", instead you should do: // function getPerson(uint _index) public view returns (uint, string memory){...} // but returning Person seems to work? function GetPerson(uint _index) public view returns (Person memory){ return people[_index]; } // BUT, if we do return multiple values like recommended by Filip, this is how: function GetPersonValues(uint _index) public view returns (uint, string memory){ Person memory personToReturn = people[_index]; return (personToReturn.age, personToReturn.name); } } ////// INTERMEDIATE ////// // commented out with all comments, see re-created Bank contract below // that's broken out using inheritance // contract Bank { // // MAPPINGS // // https://academy.moralis.io/lessons/mappings-introduction // // https://academy.moralis.io/lessons/mappings-2 // mapping(address => uint) balance; // // see below for re-declaration of Deposit() being reserved for // // the owner address. // // function Deposit(uint _amount) public returns(uint){ // // balance[msg.sender] += _amount; // // return balance[msg.sender]; // // } // function GetBalance() public view returns(uint){ // return balance[msg.sender]; // } // // IMPLEMENTING VISIBILITY // // https://academy.moralis.io/lessons/introduction-to-visibility-2 // // https://academy.moralis.io/lessons/implementing-visibility // // AND ERROR HANDLING // // https://academy.moralis.io/lessons/require-theory // // https://academy.moralis.io/lessons/assert-invariants-theory // // https://academy.moralis.io/lessons/require // // https://academy.moralis.io/lessons/assert // // MODIFIERS // // https://academy.moralis.io/lessons/modifiers-2 // address owner; // // don't need to pass the address in, msg.sender works here // // constructor(address _owner){ // constructor(){ // owner = msg.sender; // } // // modifiers used to restrict access to functions // modifier onlyOwner { // require(msg.sender == owner, "owner address missing"); // _; // means to: run the function // // but what actually happens is, the function logic calling this // // modifer get's placed right here. // } // modifier costs(uint _price){ // require(msg.value >= _price); // _; // } // // used like, where value needs to be a static value: // // function doSomething() public costs(100) {...} // // Deposit get's redefined again below to be payable: // // function Deposit(uint _amount) public onlyOwner returns(uint){ // // balance[msg.sender] += _amount; // // // emit event (see EVENTS below): // // emit balanceAdded(_amount, msg.sender); // // emit indexedBalanceAdded(_amount, msg.sender); // // return balance[msg.sender]; // // } // function Transfer(address _recipient, uint _amount) public { // // check if sender has balance first // require(balance[msg.sender] >= _amount, "insufficient balance"); // require(msg.sender != _recipient, "must send to a separate address"); // should never allow this, doesn't make sense and might cause glitches/bugs // uint previousBalance = balance[msg.sender]; // // break out re-usable code into private functions // // balance[msg.sender] -= _amount; // // balance[_recipient] += _amount; // _transfer(msg.sender, _recipient, _amount + 10); // // NOTE: // // assert(false); // // will consume all remaining gas in the function call // assert(balance[msg.sender] == previousBalance - _amount); // unsure how to set custom err: "new balance doesn't match expected value" // // event logs & further checks // // TODO // } // // NOTE: private functions naming scheme is also using "_" prepended // // to the function name, but you can name it anything. // function _transfer(address _from, address _to, uint _amount) private { // balance[_from] -= _amount; // balance[_to] += _amount; // } // // DATA LOCATION // // https://academy.moralis.io/lessons/data-location-2 // // 3 types: // // - storage: permanent data storage (state variables defined outside of functions) // // - memory: temporary data storage (functions parameters and local variables) // // - calldata: similar to memory, but READ-ONLY (used for cheaper gas as it's more optimized, it's like a constant ) // // EVENTS // // https://academy.moralis.io/lessons/events-3 // // Normal event: // event deposited(uint _amount, address _toAddress); // // Indexed (searchable in nodes) event: // // NOTE: limited to 3 indexed parameters per-event. // event indexedDeposited(uint _amount, address indexed _toAddress); // // this is how it's triggered in a function (see Deposit()): // // emit balanceAdded(amount, msg.sender); // // PAYABLE FUNCTIONS // // https://academy.moralis.io/lessons/payable-functions-3 // // This deposit's from the Address calling Deposit() // // to the Smart Contracts address. // // function Deposit() public payable returns(uint){ // function Deposit() public payable{ // // NOTE: not required, this is only for us to internally keep track. // // The ETH used happens automatically. // balance[msg.sender] += msg.value; // // emit event (see EVENTS below): // emit deposited(msg.value, msg.sender); // // emit indexedDeposited(msg.value, msg.sender); // // return balance[msg.sender]; // } // event withdrawn(uint _amount, address _toAddress); // function Withdraw(uint _amount) public returns(uint){ // require(balance[msg.sender] >= _amount, "not enough funds"); // // msg.sender.transfer() doesn't work on normal address types // // need to make msg.sender to address payable type: // address payable sender = payable(msg.sender); // // NOTE: for security purposes, ALWAYS modify contract state before // // transfering any funds. // // see this forum comment: https://studygroup.moralis.io/t/transfer-assignment/27368/24 // uint oldBalance = balance[msg.sender]; // balance[msg.sender] -= _amount; // // Transfer to the sender: // // NOTE: if transfer fails, it will refend just like with require() // sender.transfer(_amount); // assert(balance[msg.sender] == oldBalance - _amount); // emit withdrawn(_amount, sender); // return balance[msg.sender]; // } // } // INHERITANCE // https://academy.moralis.io/lessons/inheritance-reading-assignment-2 // https://academy.moralis.io/lessons/inheritance-2 import "./helloworld-inheritance-Ownable.sol"; // EXTERNAL CONTRACTS // https://academy.moralis.io/lessons/external-contracts-2 interface GovernmentInterface { function AddTransaction(address _from, address _to, uint _amount) external; function SendValueExample() external payable; // NOTE: can only have externally declared functions in an interface. // function GetTransaction(uint _txID) public view returns(Transaction memory); } // NOTE: lots of comments removed, see original Bank contract with course // notes/comments/links above. // Bank is now the child contract to Ownable parent contract. contract Bank is Ownable { GovernmentInterface GovernmentInstance = GovernmentInterface(0xC588fFb141b4cFc405BD87BB4793C49eAA4E9Bf5); mapping(address => uint) balance; function GetBalance() public view returns(uint){ return balance[msg.sender]; } event transfered(uint _amount, address _recipient); function Transfer(address _recipient, uint _amount) public { // check if sender has balance first require(balance[msg.sender] >= _amount, "insufficient balance"); require(msg.sender != _recipient, "must send to a separate address"); // should never allow this, doesn't make sense and might cause glitches/bugs uint previousBalance = balance[msg.sender]; // break out re-usable code into private functions // balance[msg.sender] -= _amount; // balance[_recipient] += _amount; _transfer(msg.sender, _recipient, _amount); GovernmentInstance.AddTransaction(msg.sender, _recipient, _amount); // can send value as: // X // not specifying a type defaults to wei // X wei // X gwei // X ether GovernmentInstance.SendValueExample{value: 1 wei}(); // NOTE: // assert(false); // will consume all remaining gas in the function call assert(balance[msg.sender] == previousBalance - _amount); // unsure how to set custom err: "new balance doesn't match expected value" // event logs & further checks emit transfered(_amount, _recipient); } // NOTE: private functions naming scheme is also using "_" prepended // to the function name, but you can name it anything. function _transfer(address _from, address _to, uint _amount) private { balance[_from] -= _amount; balance[_to] += _amount; } // Normal event: event deposited(uint _amount, address _toAddress); // Indexed (searchable in nodes) event: // NOTE: limited to 3 indexed parameters per-event. event indexedDeposited(uint _amount, address indexed _toAddress); // This deposit's from the Address calling Deposit() // to the Smart Contracts address. // function Deposit() public payable returns(uint){ function Deposit() public payable{ // NOTE: not required, this is only for us to internally keep track. // The ETH used happens automatically. balance[msg.sender] += msg.value; // emit event (see EVENTS below): emit deposited(msg.value, msg.sender); // emit indexedDeposited(msg.value, msg.sender); // return balance[msg.sender]; } event withdrawn(uint _amount, address _toAddress); function Withdraw(uint _amount) public returns(uint){ require(balance[msg.sender] >= _amount, "not enough funds"); // msg.sender.transfer() doesn't work on normal address types // need to make msg.sender to address payable type: address payable sender = payable(msg.sender); // NOTE: for security purposes, ALWAYS modify contract state before // transfering any funds. // see this forum comment: https://studygroup.moralis.io/t/transfer-assignment/27368/24 uint oldBalance = balance[msg.sender]; balance[msg.sender] -= _amount; // Transfer to the sender: // NOTE: if transfer fails, it will refend just like with require() sender.transfer(_amount); assert(balance[msg.sender] == oldBalance - _amount); emit withdrawn(_amount, sender); return balance[msg.sender]; } }