Last active
January 14, 2022 10:58
-
-
Save tomstorms/5d1d41207914c0161594df1572557ced to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #----------------------------------------------------- | |
| # To learn, see: https://cryptozombies.io | |
| #----------------------------------------------------- | |
| # Terminology | |
| wei = the smallest sub-unit of Ether — there are 10^18 wei in one ether | |
| "Proof of Work" = first to solve a computationally-intensive mathematical problem | |
| ERC20 token = used in exchanges as a cryptocurrency | |
| ERC721 token = crypto-collectible; unique | |
| #----------------------------------------------------- | |
| # Types | |
| uint dnaDigits = 16; | |
| uint levelUpFee = 0.001 ether; // 0.001 = the value; ether = the unit | |
| struct Zombie { | |
| string name; | |
| uint dna; | |
| } | |
| Example on saving gas: | |
| - uint will always assign 256; not efficient | |
| struct NormalStruct { | |
| uint a; | |
| uint b; | |
| uint c; | |
| } | |
| struct MiniMe { | |
| uint32 a; | |
| uint32 b; | |
| uint c; | |
| } | |
| // `mini` will cost less gas than `normal` because of struct packing | |
| NormalStruct normal = NormalStruct(10, 20, 30); | |
| MiniMe mini = MiniMe(10, 20, 30); | |
| #----------------------------------------------------- | |
| # Mapping | |
| // public | |
| mapping (uint => address) public zombieToOwner; | |
| zombieToOwner[id] = msg.sender; | |
| mapping (address => uint) ownerZombieCount; | |
| ownerZombieCount[msg.sender]++; | |
| #----------------------------------------------------- | |
| # Events | |
| event NewZombie(uint zombieId, string name, uint dna); | |
| emit NewZombie(id, _name, _dna); | |
| #----------------------------------------------------- | |
| # Functions | |
| // private doesn't return | |
| function _createZombie(string memory _name, uint _dna) private { | |
| // private returns | |
| function _generateRandomDna(string memory _str) private view returns (uint) { | |
| // public | |
| function createRandomZombie(string memory _name) public { | |
| // public returns | |
| function whatIsMyNumber() public view returns (uint) { | |
| // payable function | |
| function buySomething() external payable { | |
| function modifiers: | |
| private = callable from other functions inside the contract | |
| internal = callable from other functions inheriting the contract | |
| external = called outside the contract; cant be called inside contract | |
| public = called anywhere, internal + external | |
| view = no data will be saved/changed in the function | |
| pure = does not read or write data from the blockchain | |
| both dont cost gas if called externally; will cost if called internally | |
| payable = receive Ether. the contract will hold the ether value until withdrawn | |
| #----------------------------------------------------- | |
| # Modifier | |
| modifier olderThan(uint _age, uint _userId) { | |
| require (age[_userId] >= _age); | |
| _; | |
| } | |
| #----------------------------------------------------- | |
| # Security | |
| - examine all your public and external functions | |
| - overflows and underflows | |
| #----------------------------------------------------- | |
| # Interface with other contracts | |
| // Example External contract: | |
| contract LuckyNumber { | |
| mapping(address => uint) numbers; | |
| function setNum(uint _num) public { | |
| numbers[msg.sender] = _num; | |
| } | |
| function getNum(address _myAddress) public view returns (uint) { | |
| return numbers[_myAddress]; | |
| } | |
| } | |
| // Our interface to the example external contract (you can copy and paste the function declaration from the external contract); | |
| contract NumberInterface { | |
| function getNum(address _myAddress) public view returns (uint); | |
| } | |
| // Example to connect the interface with the contract address | |
| contract MyContract { | |
| address NumberInterfaceAddress = 0xab38... | |
| // ^ The address of the FavoriteNumber contract on Ethereum | |
| NumberInterface numberContract = NumberInterface(NumberInterfaceAddress); | |
| // Now `numberContract` is pointing to the other contract | |
| function someFunction() public { | |
| // Now we can call `getNum` from that contract: | |
| uint num = numberContract.getNum(msg.sender); | |
| // ...and do something with `num` here | |
| } | |
| } | |
| dealing with return types: | |
| // example of function | |
| function getKitty(uint256 _id) external view returns ( | |
| bool isGestating, | |
| bool isReady, | |
| uint256 cooldownIndex, | |
| uint256 nextActionAt, | |
| uint256 siringWithId, | |
| uint256 birthTime, | |
| uint256 matronId, | |
| uint256 sireId, | |
| uint256 generation, | |
| uint256 genes | |
| ); | |
| // in a function you can return and assign data like this: | |
| function feedOnKitty(uint _zombieId, uint _kittyId) public { | |
| uint kittyDna; | |
| (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); | |
| // kittyDna = you can now use this as a variable in this function | |
| feedAndMultiply(_zombieId, kittyDna); | |
| } | |
| #----------------------------------------------------- | |
| # for loop | |
| uint counter = 0; | |
| for (uint i = 1; i <= 10; i++) { | |
| // If `i` is even... | |
| if (i % 2 == 0) { | |
| // Add it to our array | |
| evens[counter] = i; | |
| // Increment counter to the next empty index in `evens`: | |
| counter++; | |
| } | |
| } | |
| #----------------------------------------------------- | |
| # Global Variables/Functions | |
| msg.sender = the address of the person who called the function | |
| msg.value = value of ether sent to the contract | |
| ether = built-in unit | |
| require() = throw error and stop if true | |
| ; = required on every line | |
| storage = permanently store in blockchain (state variables) | |
| memory = temporarily store in blockchain | |
| now = current unix timestamp | |
| seconds, minutes, hours, days, weeks. years = time variables | |
| calldata = is somehow similar to memory, but it's only available to external functions | |
| view = only read data from the blockchain. dont cost gas. no need to create a transaction to read data | |
| overflow = number resets past it's maximum | |
| underflow = subtracting and going into negative | |
| contract = keyword for building a function | |
| library = keyword for allowing us to use the using keyword and extract methods from a library | |
| indexed = use data for filtering | |
| use // for single line comments | |
| use /* */ for multi line comments | |
| natspec format: | |
| @title and @author are straightforward | |
| @notice explains to a user what the contract / function does | |
| @dev is for explaining extra details to developers. | |
| @param and @return are for describing what each parameter and return value of a function are for | |
| #----------------------------------------------------- | |
| # Inheritance | |
| contract ZombieFactory {} | |
| contract ZombieFeeding is ZombieFactory {} | |
| // multiple | |
| contract SatoshiNakamoto is NickSzabo, HalFinney {} | |
| Use different files: | |
| import "./someothercontract.sol"; | |
| #----------------------------------------------------- | |
| # OpenZeppelin | |
| OpenZeppelin's Ownable contract | |
| import "./ownable.sol"; | |
| contract ZombieFactory is Ownable { | |
| // then you can use the onlyOwner modifier | |
| function setKittyContractAddress(address _address) external onlyOwner { | |
| owner() and onlyOwner from the Ownable contract | |
| function withdraw() external onlyOwner { | |
| address payable _owner = address(uint160(owner())); | |
| _owner.transfer(address(this).balance); | |
| } | |
| SafeMath library = prevents overflow/underflows | |
| import "./safemath.sol"; | |
| contract ZombieFactory is Ownable { | |
| using SafeMath for uint256; | |
| using SafeMath32 for uint32; | |
| using SafeMath16 for uint16; | |
| #----------------------------------------------------- | |
| # keccak256 | |
| Random number generation = this can be hackable! use an oracle | |
| keccak256 hash function | |
| random number between 1 and 100; | |
| uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100; | |
| #----------------------------------------------------- | |
| # FRONTEND | |
| Web3.js: | |
| - cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); | |
| - call is used for view and pure functions | |
| - return cryptoZombies.methods.zombieToOwner(id).call() | |
| - send function changes data in your smart contract; will cost gas | |
| subscribe to events: | |
| cryptoZombies.events.NewZombie().on("data", function(event) { | |
| let zombie = event.returnValues; | |
| // We can access this event's 3 return values on the `event.returnValues` object: | |
| console.log("A new zombie was born!", zombie.zombieId, zombie.name, zombie.dna); | |
| }).on("error", console.error); | |
| can filter data in the events wherever the indexed keyword is used: | |
| cryptoZombies.events.Transfer({ filter: { _to: userAccount } }).on("data", function(event) { | |
| let data = event.returnValues; | |
| // The current user just received a zombie! | |
| // Do something here to update the UI to show it | |
| }).on("error", console.error); | |
| #----------------------------------------------------- | |
| # Metamask | |
| var accountInterval = setInterval(function() { | |
| // Check if account has changed | |
| if (web3.eth.accounts[0] !== userAccount) { | |
| userAccount = web3.eth.accounts[0]; | |
| // Call a function to update the UI with the new account | |
| getZombiesByOwner(userAccount) | |
| .then(displayZombies); | |
| } | |
| }, 100); | |
| #----------------------------------------------------- | |
| # Truffle Tests | |
| - Setup and initialise class for tests | |
| let [alice, bob] = accounts; | |
| beforeEach(async () => { | |
| contractInstance = await CryptoZombies.new(); | |
| }); | |
| - To group tests, Truffle provides a function called context: | |
| xcontext("with the single-step transfer scenario", async () => { | |
| it("should transfer a zombie", async () => { | |
| }) | |
| }) | |
| - combine with Chai: | |
| var expect = require('chai').expect; | |
| let lessonTitle = "Testing Smart Contracts with Truffle"; | |
| expect(lessonTitle).to.be.a("string"); | |
| #----------------------------------------------------- | |
| # Truffle Config (truffle-config.js) | |
| - Make sure your Solidity version matches your contracts. You can override Truffle config: | |
| { | |
| ... | |
| compilers: { | |
| solc: { | |
| version: "0.8.0" | |
| #----------------------------------------------------- | |
| # Truffle Commands | |
| - truffle init = starts a new project | |
| - truffle develop = starts the cli development environment | |
| Once the cli is running you can use the following commands: | |
| - migrate --reset = deploy contracts and ignore cache | |
| - migrate --reset --network kovan = deploy contracts to kovan network. make sure the settings are right in truffle-config.js | |
| - deploy --compile-none --network bsctestnet = will display to the network | |
| - test = executes any tests up | |
| #----------------------------------------------------- | |
| # Import Truffle web3 accounts into Metamask | |
| - truffle develop - will provide you the following: | |
| ... | |
| Mnemonic: Strange Purple Adamant Crayons Entice Fun Eloquent Missiles | |
| ^ this of course is random, but you need to copy this phrase | |
| - Go to your metamask | |
| - Logout of any existing accounts | |
| - Then on the home of the metamask click "Import using account seed phase" | |
| - Paste the mnemonic phrase from truffle | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment