- references
- https://www.oreilly.com/library/view/hands-on-smart-contract/9781492045250/
- https://www.amazon.com/Solidity-Programming-Essentials-building-contracts/dp/1803231181
- https://www.amazon.com/Beginning-Ethereum-Smart-Contracts-Programming/dp/1484292707
- https://www.springerprofessional.de/en/ethereum-smart-contract-development-in-solidity/18334966
- https://www.manning.com/books/blockchain-in-action
- https://www.packtpub.com/product/mastering-blockchain-programming-with-solidity/9781839218262
- https://www.algoexpert.io/blockchain/index
- https://chat.openai.com
- https://www.linkedin.com/pulse/what-token-burning-how-does-work-azhar-siddiqui
- https://medium.com/cryptronics/ethereum-smart-contract-security-73b0ede73fa8
- https://medium.com/immunefi/the-ultimate-guide-to-reentrancy-19526f105ac
- https://medium.com/neptune-mutual/understanding-signature-replay-attack-cbb70a7f46d8
- https://slowmist.medium.com/intro-to-smart-contract-security-audit-signature-replay-b71c23910629
- https://ethereum.stackexchange.com/questions/26/what-is-a-replay-attack
- https://mirror.xyz/0xbuidlerdao.eth/lOE5VN-BHI0olGOXe27F0auviIuoSlnou_9t3XRJseY
- https://stackoverflow.com/questions/74164255/x19ethereum-signed-message-n32-prefix-meaning
- https://ethereum.stackexchange.com/questions/128552/why-would-you-use-a-signed-message-to-verify-the-sender
- https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7
- https://medium.com/@kaishinaw/signing-and-verifying-ethereum-messages-f5acd41ca1a8
- ethers-io/ethers.js#555
- https://medium.com/@codetractio/walkthrough-of-an-ethereum-improvement-proposal-eip-6fda3966d171
- https://medium.com/@moplabs/eip-155-with-mopai-75b322962d1b
- https://medium.com/coinmonks/eip712-a-full-stack-example-e12185b03d54
- https://medium.com/coinmonks/ethereum-signatures-for-hackers-and-auditors-101-4da766cd6344
- https://blog.hook.xyz/validate-eip-712/
- https://ethereum.stackexchange.com/questions/125128/what-is-domain-separator-in-eip712
- https://kristaps.me/blog/solidity-eip-712-sign-ethers-js/
- https://dev.to/fassko/what-are-meta-transactions-the-eip-712-standard-and-how-to-sign-a-message-with-metamask-4mil
- https://medium.com/metamask/eip712-is-coming-what-to-expect-and-how-to-use-it-bb92fd1a7a26
- https://0xsomeone.medium.com/b002-solidity-ec-signature-pitfalls-b24a0f91aef4
- https://nfting.medium.com/what-is-a-replay-attack-23e39ebbb11a
- https://medium.com/coinmonks/what-the-heck-is-replay-protection-aae910f2a3cb
- https://medium.com/@MyPaoG/explaining-the-dao-exploit-for-beginners-in-solidity-80ee84f0d470
- https://medium.com/coinmonks/understanding-and-preventing-short-address-attacks-in-solidity-smart-contracts-3d46a4af9a15
- https://www.reddit.com/r/ethereum/comments/6r9nhj/cant_understand_the_erc20_short_address_attack/
- https://medium.com/huzzle/ico-smart-contract-vulnerability-short-address-attack-31ac9177eb6b
- https://medium.com/coinmonks/what-is-an-nft-and-how-does-it-works-1f85a5734f84
- https://johankristensson.medium.com/explaining-nft-technology-simplifying-the-basics-89aa539644ee
- https://medium.com/coinmonks/what-are-nfts-non-fungible-tokens-explained-f3a3d8d18ed7
- https://webdevelopmentkl.medium.com/nfts-explained-everything-you-need-to-know-323b6fa740d5
- https://medium.com/analytics-vidhya/what-is-nft-characteristics-and-use-cases-explained-ad7dc77f350f
- https://medium.com/@BryanBulte/nft-explained-simply-f4daee993096
- https://medium.com/coinmonks/nft-smart-contract-development-543d712003ef
- https://medium.com/@dropspace/nft-smart-contract-understanding-the-backbone-of-digital-collectibles-a1f91538c3be
- https://thebojda.medium.com/code-a-minimalistic-nft-smart-contract-in-solidity-on-ethereum-a-how-to-guide-hacker-noon-5cb72f0891c
- https://medium.com/@kaishinaw/creating-truly-decentralised-nfts-a-comprehensive-guide-to-erc721-ipfs-b2ae60e312b6
- https://medium.com/@JaysNotebook/learning-openzeppelin-everything-you-need-to-know-5152a0ad8c4
- https://medium.com/@ethdapp/using-the-openzeppelin-escrow-library-6384f22caa99
- https://stermi.medium.com/lets-play-ethernaut-ctf-learning-solidity-security-while-playing-1678bd6db3c4
- https://www.blockchain-council.org/ethereum/beginners-guide-what-is-erc20/
- https://ethereum.org/en/developers/docs/standards/tokens/erc-20/
- https://www.ledger.com/academy/what-are-erc-tokens-and-why-do-we-use-them
- https://medium.com/@ajaotosinserah/introduction-to-erc20-token-ad30b7422db9
- https://medium.com/coinmonks/what-are-erc-20-tokens-afcccbc53962
- https://medium.com/theblockchainhub/erc20-introduction-part-1-e8c2062aed0f
- https://medium.com/coinmonks/solidity-lesson-28-understanding-the-erc-20-token-928758f053e1
- https://medium.com/@eiki1212/what-is-erc-20-explanation-of-details-eacf9f288f8b
- https://medium.com/@ankesh27/erc20-erc721-erc1155-token-standards-explained-f4d240a9c084
- https://medium.com/geekculture/code-your-own-erc-20-token-1678d9b381da
- https://medium.com/coinmonks/introduction-to-token-standards-for-ethereum-part-3-extensions-foundations-for-the-erc20-721-22bb727f072c
- https://medium.com/@lauraguy/what-is-an-erc20-token-d7320369bf76
- https://medium.com/hackernoon/the-innards-of-an-erc20-token-587c29e9b8a1
- https://medium.com/@danielque/what-we-learned-from-auditing-the-top-20-erc20-token-contracts-7526ef3b6fb1
- https://medium.com/@jgm.orinoco/understanding-erc-20-token-contracts-a809a7310aa5
- https://vitto.cc/how-to-create-and-deploy-an-erc20-token-in-20-minutes/
- https://medium.com/coinmonks/my-first-erc20-token-7d5d16632818
- https://medium.com/@infuyIT/understanding-erc-standards-a-guide-to-different-types-of-blockchain-tokens-e7991e4ecd61
- https://www.blockchain-council.org/ethereum/erc20-vs-erc721/
- https://medium.com/quick-programming/what-is-erc165-and-why-you-should-use-it-d0641a2f29e5
- https://medium.com/@chiqing/ethereum-standard-erc165-explained-63b54ca0d273
- https://ethereum.stackexchange.com/questions/83561/why-use-the-erc165-standard
- https://eips.ethereum.org/EIPS/eip-165
- https://docs.openzeppelin.com/contracts/
- https://techjd.medium.com/what-is-supportsinterface-understanding-erc165-503b40b942a6
- https://ethereum.stackexchange.com/questions/44880/erc-165-query-on-erc-721-implementation
- https://www.quicknode.com/guides/ethereum-development/nfts/how-to-create-and-deploy-an-erc-1155-nft#:~:text=What%20is%20ERC1155%3F,were%20required%20to%20achieve%20this.
- https://blog.thirdweb.com/what-is-erc-1155-nft/
- https://docs.openzeppelin.com/contracts/5.x/erc1155
- https://decrypt.co/resources/what-is-erc-1155-ethereums-flexible-token-standard
- https://ethereum.org/en/developers/docs/standards/tokens/erc-1155/
- https://www.linkedin.com/pulse/erc-1155-demystified-deep-dive-versatile-token-asset-mukul-tripathi/
- https://moralis.io/erc-1155-nfts-what-is-the-erc-1155-standard/
- https://limechain.tech/blog/erc-721-vs-erc-1155-ethereum-token-standards/
- https://www.leewayhertz.com/erc-20-vs-erc-721-vs-erc-1155/
- https://mightyblock.co/blog/news/how-to-build-and-use-erc-1155-tokens/
- https://medium.com/@konradmgnat/what-is-the-erc1155-token-and-when-to-use-it-fc53b5d001ca
- https://medium.com/codex/token-standards-erc-20-vs-erc-721-vs-erc-1155-2e4a09dc0f8a
- https://medium.com/envienta-open-source-everything/erc-1155-non-fungible-tokens-on-steroids-71aab96fa674
- https://pancy.medium.com/building-event-tickets-with-erc-1155-contract-1c427d89a77d
- https://soenkeba.medium.com/truly-decentralized-nfts-by-erc-1155-b9be28db2aae
- https://medium.com/@Consensys/an-introduction-to-ipfs-9bba4860abd0#:~:text=At%20its%20core%2C%20IPFS%20is,also%20a%20distributed%20file%20system.
- https://whatdoesthequantsay.com/2015/09/13/ipfs-introduction-by-example/
- https://medium.com/geekculture/what-is-ipfs-the-inter-planetary-file-system-explained-40744a7ae95a
- https://medium.com/aleph-im/ipfs-explained-in-2min-24e10afdb191
- https://medium.com/@sjarancio/ipfs-what-it-is-how-it-works-and-why-its-needed-49b75d8e857b
- https://medium.com/virtuslab/the-complete-beginners-guide-to-ipfs-9713a6f59193
- https://medium.com/@akshay_111meher/how-ipfs-works-545e1c890437
- https://ipfs.io/ipfs/QmRU1jJ1kNd9fTzjFwM4X9YtA2wfXN1W2eFK7mgTMJ8xgK
- https://medium.com/coinmonks/the-technology-behind-ipfs-and-what-can-ipfs-do-c7009fe42bab
- https://itsromiljain.medium.com/ipfs-the-permanent-distributed-web-7a0d3ede10af
- https://itsromiljain.medium.com/ipfs-the-permanent-distributed-web-continues-ffbe1919bb94
- https://medium.com/pinata/what-is-an-ipfs-pinning-service-f6ed4cd7e475
- https://medium.com/hackernoon/ipfs-and-merkle-forest-a6b7f15f3537
- https://medium.com/hackernoon/a-beginners-guide-to-ipfs-20673fedd3f
- https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507
- https://medium.com/textileio/whats-really-happening-when-you-add-a-file-to-ipfs-ae3b8b5e4b0f
- https://medium.com/textileio/how-ipfs-peer-nodes-identify-each-other-on-the-distributed-web-8b5b6476aa5e
- https://medium.com/@Nico_Vergauwen/create-your-own-ethereum-token-part-2-erc223-3076f764cf62
- https://ethereum.stackexchange.com/questions/120996/what-is-the-difference-between-safetransferfrom-and-transferfrom-functions-i
- https://ethereum.stackexchange.com/questions/73125/what-is-the-bytes-data-param-in-safetransferfrom
- it may be worthwile to take a look here: https://github.com/mtumilowicz/solidity-basics-workshop
- goals of this workshop
- know best practices when developing smart contract with Solidity
- understand some basic attacks & how to protect against them
- introduction to tokens
- erc20, erc721, erc1155
- enumerating some standard design patterns in Solidity
- understanding basic of IPFS
- introduction to OpenZeppelin
- workshop task
- implement CustomNftToken - no third party libraries
- no third party libraries
- mint - everyone, but cost 1 eth
- transfer - only owner of token can transfer it
- withdraw - only owner of contract can get money
- implement tests in Solidity
- rewrite it using Ownable from OpenZeppelin
- no third party libraries
- OpenZeppelin
- implement Erc721Token
- should define miner role and restrict minting only to miners
- should define admin role and restrict burning only to admins
- uri should be configurable per every token (ipfs case)
- implement Erc20Token
- implement Erc1155Token
- fungible: GOLD, SILVER, SWORD, SHIELD
- non-fungible: THOR_HAMMER
- implement Erc721Token
- implement CustomNftToken - no third party libraries
- don't use plain secret on-chain
- problem: front-running attacks
- all the transaction data is open and can be seen by others
- even data of pending transaction can be seen by others
- example: domain name registration
- user is registering a unique value
- attacker watch for the transactions on that contract
- send the high gas-price transaction to front run the user's transaction
- all the transaction data is open and can be seen by others
- solution: commit-and-reveal scheme
- hash of the original secret is submitted to the blockchain
- steps
- all parties submit their secret hash
- all parties reveal their choice by submitting salt (that was used to generate the secret hash)
- problem: front-running attacks
- don't use
tx.origin
for authorization- problem: intercepting transaction
- we have
Vault
contract, that has functionwithdraw()
usingtx.origin
for authorization - attacker deploys AttackerContract
- attacker ask the original owner of the Vault contract to send some ether to the AttackerContract
- AttackerContract calls the Vault.withdraw() function
- we have
- solution: always use
msg.sender
- problem: intercepting transaction
- avoid dependency on untrusted external calls
- problems
- if the target contract is killed via selfdestruct, the external call to the function will always fail
- reentrancy attack
- re-enter origin contract before the state changes are finalized
- unpredictable gas costs
- problems
- reentrancy attacks
- problem: re-enter origin contract before the state changes are finalized
- example (2016 dao hack)
function withdraw(uint amount) public { require(balanceOf[msg.sender] >= amount); msg.sender.call{value: amount}(""); // invokes fallback function in caller, which in turns invokes withdraw again balanceOf[msg.sender] -= amount; Withdrawal(msg.sender, amount); }
- example (2016 dao hack)
- solution: checks-effects-interactions (CEI) pattern
- example
function withdraw(uint amount) public { // Checks phase require(balanceOf[msg.sender] >= amount, "Insufficient balance"); // Effects phase balanceOf[msg.sender] -= amount; // Interactions phase (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); // Emit event after successful interaction emit Withdrawal(msg.sender, amount); }
- example
- problem: re-enter origin contract before the state changes are finalized
- replay attack
- problem
- cross-chain signature replay
- example
function deposit() public payable { // reply transaction from testnet on the mainnet balances[msg.sender] += msg.value; }
- usually happen during chain splits or hard forks
- Ethereum: signed transaction is valid for all Ethereum chains
- Bitcoin: addresses in testnet use a different prefix from addresses in mainnet
- keys are different
- solution: use chainId when generating a signature
- example
- same-chain signature replay
- example
function deposit() public payable { // attacker rebroadcasts the same transaction with the same parameters balances[msg.sender] += msg.value; }
- solution: use nonce when generating a signature
- example
- cross-chain signature replay
- solution
- two approaches
- strong replay protection
- one of the forked chains will make it mandatory to change some information in the transaction for it to be valid over its network
- opt-in reply protection
- users must make manual changes to the transaction to ensure that they won’t be replayed
- example: Ethereum Classic (ETC) did not implement strong replay protection during the hard fork
- strong replay protection
- EIP155
- called "simple replay attack protection"
- defines the chainID field in Ethereum transactions to prevent replay attacks
- before EIP155
- there are 6 inputs to an Ethereum transaction
- nonce, gasPrice, gasLimit, to, value, data
- transaction is not chain specific
- same addresses in different networks => can lead to unintended transactions
- there are 6 inputs to an Ethereum transaction
- user should sign the data along with a unique nonce value each time
- example
mapping(address => uint256) public nonces; function deposit(uint256 nonce) public payable { require(nonce > nonces[msg.sender], "Invalid nonce"); nonces[msg.sender] = nonce; balances[msg.sender] += msg.value; }
- example
- every transaction signature should also encapsulate a unique identifier for the specific network
- EIP191
- called "signature data standard"
- introduces a prefix to the data that is being signed
- example
"\x19Ethereum Signed Message:\n32"
- byte
0x19
standardized because an already existing implementation (in the Go Ethereum client software) was using it before the standard was finalized - last number
32
is the byte length of the message (excluding the prefix)
- byte
- example
- reason for prefixing is so that a cleverly designed message cannot possibly be a valid transaction
- allowing signing raw messages, without a prefix, enables an app to steal all ether, tokens and assets
- purpose is entirely to invalidate any payload as a valid RLP encoded transaction
- MetaMask does not permit you to perform this operation
- it will always force prefixing a signed message even when the message is a hash
- when you create a transaction: unsigned transaction -> hash it -> sign it
- when you create a message: unsigned message -> prefix it -> hash it -> sign it
- example
decoded with https://flightwallet.github.io/decode-eth-tx/
let unsignedTransaction = "0xe980850218711a00825208948ba1f109551bd432803012645ac136ddd64dba72880de0b6b3a764000080";
{ "nonce": 0, "gasPrice": 9000000000, "gasLimit": 21000, "to": "0x8ba1f109551bd432803012645ac136ddd64dba72", "value": 1000000000000000000, "data": "" }
- if attacked gives you this hash and you sign it => it is now a valid signed transaction which will send 1 ether to attacker
- string that begins with "\x19Ethereum Signed Message:" is not a valid transaction
- it is safe to sign it
- allowing signing raw messages, without a prefix, enables an app to steal all ether, tokens and assets
- eliminates the risk of replay attacks on other EVM platforms
- if there were no platform-specific prefixes, the resulting signature would be the same for all platforms
- use case
- smart contract needs to verify a signed message
- external systems need to interact with Ethereum transactions in a standardized way
- list of chain ids: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md?ref=blog.hook.xyz#list-of-chain-ids
- EIP712
- is a more advanced and secure method of signing a transaction
- provides a way to hash and sign typed structs rather than just strings
- uses the keccak256 hashing algorithm
- requires TypedData (JSON object input)
- example
typedData := apitypes.TypedData{ Types: types, PrimaryType: "ERC721Order", Domain: domain, Message: message, }
- includes the following properties
- types
- used to define the structs that will be used in the message and specify their types
- is a mapping of string to a Type array
- example
types := apitypes.Types{ "EIP712Domain": { {Name: "name", Type: "string"}, {Name: "version", Type: "string"}, {Name: "chainId", Type: "uint256"}, {Name: "verifyingContract", Type: "address"}, }, "ERC721Order": { {Name: "direction", Type: "uint8"}, ... }, "Fee": { // custom types {Name: "recipient", Type: "address"}, {Name: "amount", Type: "uint256"}, {Name: "feeData", Type: "bytes"}, },
- example
- domain
- information specific to the protocol contract that the dapp used when asking for a signature
- designed to include bits of DApp unique information
- name
- human-readable string that represents the name of the domai
- often used to identify the dApp or smart contract associated with the message
- version
- string representing the version of the domain
- can be useful for distinguishing between different versions of the same dApp or smart contract
- chainId
- wallet providers should prevent signing if it does not match the network it is currently connected to
- it is crucial that chainId is verified on-chain
- contracts have no way to find out which chain ID they are on
- developers must hardcode chainId into their contracts and take extra care to make sure that it corresponds to the network they deploy on
- verifyingContract
- address of the smart contract that will verify the signed message
- name
- domain separator
- information from the domain also needs to be hashed and used as a domain separator
- purpose is to disambiguate between two dapps with identical structures
- in order to avoid generating the same signatures for both
- example
- two DApps come up with an identical structure like Transfer(address from,address to,uint256 amount)
- with domain separator the dApp developers are guaranteed that there can be no signature collision
- primaryType
- string that represents the outermost type of the message object
- message
- contains the order element names as strings mapped to their values
- example
can be split into two data structures
{ amount: 100, token: “0x….”, id: 15, bidder: { userId: 323, wallet: “0x….” } }
Bid: { amount: uint256, bidder: Identity } Identity: { userId: uint256, wallet: address }
- example
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.M rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) hashBytes := keccak256(rawData) hash := common.BytesToHash(hashBytes)
- types
- example
- standard for secure off-chain signature verification on the Ethereum blockchain
- signature verification
- process of checking that the address of the signer is equal to the address that you derive from the signature
- ecrecover
- is vulnerable
- it interprets v values of both 27 and 28 as equivalent
- it only checks if v is greater than or equal to 27
- signatures produced with both v = 27 and v = 28 will yield the same public key
- it does not halt execution when invalid signatures are supplied
- it simply returns the address 0x0
- zero address in most contracts has a special meaning (i.e. burn address)
- smart contract might incorrectly assume that the zero address is a valid signer
- it simply returns the address 0x0
- use: OpenZeppelin’s ECDSA library
- it interprets v values of both 27 and 28 as equivalent
- used to derive the address of a sender based on the digital signature
- needs assembly
- is vulnerable
- example
- replicate this formatting/hash function
struct Identity { uint256 userId; address wallet; } struct Bid { uint256 amount; Identity bidder; } string private constant IDENTITY_TYPE = "Identity(uint256 userId,address wallet)"; string private constant BID_TYPE = "Bid(uint256 amount,Identity bidder)Identity(uint256 userId,address wallet)"; uint256 constant chainId = 1; address constant verifyingContract = 0x1C56346CD2A2Bf3202F771f50d3D14a367B48070; bytes32 constant salt = 0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a558; string private constant EIP712_DOMAIN = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"; bytes32 private constant DOMAIN_SEPARATOR = keccak256(abi.encode( EIP712_DOMAIN_TYPEHASH, keccak256("My amazing dApp"), keccak256("2"), chainId, verifyingContract, )); function hashIdentity(Identity identity) private pure returns (bytes32) { return keccak256(abi.encode( IDENTITY_TYPEHASH, identity.userId, identity.wallet )); } function hashBid(Bid memory bid) private pure returns (bytes32){ return keccak256(abi.encodePacked( "\\x19\\x01", DOMAIN_SEPARATOR, keccak256(abi.encode( BID_TYPEHASH, bid.amount, hashIdentity(bid.bidder) )) )); function verify(address signer, Bid memory bid, sigR, sigS, sigV) public pure returns (bool) { return signer == ecrecover(hashBid(bid), sigV, sigR, sigS); }
- wallets like Metamask can display the message in a more user-friendly manner
- before EIP712
- it was difficult for users to verify the data they were asked to sign
- wallet signing interfaces would display a hashed message string
- the signer would have to assume that hash matched the message they thought they were signing
- use case
- meta-transactions
- relayer pays the gas fees on behalf of the user
- relayer = service responsible for submitting transactions on behalf of users
- users sign a request (off-chain) for a specific action they want to perform on the blockchain
- includes information like the target contract, function to call, and any required parameters
- relayer pays the gas fees on behalf of the user
- meta-transactions
- two approaches
- problem
- short address attack
- equivalent of minor SQL injection bug
- problem: leading zeros is taken from the amount, and given to the shortened address
-
- means you've multiplied your amount by 1<<8 or 256
- after the exchange has checked your balance on their internal ledger
- means you've multiplied your amount by 1<<8 or 256
- reason: EVM pads all input data with 0s
- example
- user B's address: 0xbbbbbb00
- user A's balance: 512 tokens
- A inputs B's address as 0xbbbbbb
- site (incorrectly) interprets this as a valid address and constructs transaction
- selector: 0x01020304
- transaction: 0x01020304bbbbbbbb00000001
- if we slice that back up into a 4-byte signature and 2 4-byte words
- ['0x01020304', '0xbbbbbb00', '0x000001??']
- any index into the transaction data that hasn't been provided returns 0s
- trailing zeros do not change the actual address
0x1234...5670
and0x1234...567000
represent the same address
- trailing zeros do not change the actual address
- final argument being interpreted by the EVM as 0x00000100 = 256
- 256
- given that 256 is still less than 512 (your comment), the transaction succeeds
- most vulnerable to this were large, shared wallets
- example: exchange hot wallets
-
- solution: smart contract must validate the length of an address input
- manipulating contract balance
- problem: ether can be sent forcibly to a contract
- if contract has some decision logic using
address(this).balance
- attacked can influence it - example
selfdestruct(addressOfAttackedContract)
- if contract has some decision logic using
- solution: there is no possible way to prevent forceful ether sending from happening
- problem: ether can be sent forcibly to a contract
- Ethernaut game
- Web3/Solidity based war game created by OpenZeppelin
- each level is a smart contract that needs to be ‘hacked’
- solutions: https://stermi.medium.com/lets-play-ethernaut-ctf-learning-solidity-security-while-playing-1678bd6db3c4
- security
- pull-over-push (withdrawal pattern)
- example of the problem
for(uint i = 0; i < users.length; i++) { users[i].transfer(amount); };”
- if some address is a contract it may have continually failing fallback function
- leads to whole transaction failure each time
- if some address is a contract it may have continually failing fallback function
- solution: user should be able to claim their dividend from the contract
- use cases
- send ether/token to multiple addresses
- avoid paying transaction fees (push transaction)
- transaction initiator has to pay the transaction fee
- users pay transaction fees (pull transaction)
- example of the problem
- access restriction
- restricts unauthorized function calls
- based on roles
- use modifiers to check for the access rights
- emergency stop
- ability to pause the contract functions in unwanted situations
- use cases
- contract to be handled differently in case of any emergency situations
- pull-over-push (withdrawal pattern)
- creational patterns
- factory
- create a new child contract from a parent contract
- https://eips.ethereum.org/EIPS/eip-1167
- example
- master contract can create a new child contract called Loan
- Loan contract has logic to handle contract terms and conditions along with the funds as well
- master contract can create a new child contract called Loan
- use case
- new contract is required for each request to be processed
- keep the funds separate in a different contract
- factory
- behavioral patterns
- state machine
- allows a contract to transition from different states
- enables certain functions to be executed in each state
- use cases
- contract needs to have different set of functions based on the state
- iterable map pattern
- example
mapping(uint256 => uint256) private data; uint256[] private keys; function removeValue(uint256 key) external { require(data[key] != 0, "Key does not exist"); for (uint256 i = 0; i < keys.length; i++) { if (keys[i] == key) { // Swap the element to be removed with the last element keys[i] = keys[keys.length - 1]; // Shorten the keys array by one keys.pop(); break; } } delete data[key]; }
- allows to iterate over the mapping entries
- iteration over the mapping entries should not cause an out-of-gas exception
- iteration should be used only in the view function
- does not support removal of elements
- use cases
- need to filter some data out of the mapping
- example
- whitelisted addresses
- maintain a curated list of addresses by the owner
- use cases
- whitelisted address allowed/disallowed to perform a certain task
- state machine
- gas-optimization
- worth to check: https://github.com/mtumilowicz/ethereum-gas-workshop
- keccak256 for equality check
- example: string equality
- use case
- gas-optimized solution for equality
- variable packing
- minimize slots used by storage
- each storage slot is 32 bytes
- use case
- gas-optimized solution for storage
- life cycle
- “Once a contract is destroyed, it cannot be recreated on the same address. ”
- mortal pattern allows a contract to be destroyed from the Ethereum blockchain.”
- “The mortal pattern should be used in the following cases, when:
- You do not want a contract to be present on the blockchain once its job is finished
- You want the ether held on the contract to be sent to the owner and the contract is not required further
- You do not need the contract state data after the contract reaches a specific state”
- “The mortal pattern should be used in the following cases, when:
- auto deprecate
- allows time-based access to certain function calls
- example (using chainLink oracle)
modifier onlyPremium() { require(subscriptionExpiry[msg.sender] >= getCurrentTime(), "Must be a premium member"); _; } function getCurrentTime() internal returns (uint256) { Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector); req.add("get", "https://chain.link/v1/time"); req.add("path", "now"); return sendChainlinkRequestTo(oracle, req, fee); }
- use cases
- allow/restrict a function call for a specified duration of time
- auto-expired contract
- periodic paid service-based model
- existing user can purchase a premium status for a limited duration
- stands for Interplanetary File System
- suppose humanity has colonises Mars and the first person on Mars tries to access internet services from Earth
- it would approximately take 1 hour for him to access a news website
- what if another person tries?
- he ends up taking another 1 hour and so on
- but IFPS is used, the second person on Mars shall be able to retrieve the content from the first person
- from there onwards data can spread like a wildfire
- three main principles
- unique identification via content addressing
- means that the content is going to determine the address
- once something is added it can’t be changed
- intuitive way to think about content for humans
- example: ask someone for their favorite cat video
- location answer: "the one on this server, at this sub-domain, under this file path, slash hilarious dash cat dot mp4"
- description answer: "the one where the cat knocks the glass off the counter"
- is generally not how we access content on the web today
- http
- refers objects (text files, pics, videos) by which server they are stored on
- called "location based addressing"
- location is the IP address or the domain name
- if location isn’t accessible (the server is down), you won’t get resources
- there is a high probability that someone else out there has downloaded resource and still has a copy of it
- yet your computer won’t be able to grab it from that other person
- there is a high probability that someone else out there has downloaded resource and still has a copy of it
- IPFS moves from "location based addressing" to "content based addressing"
- instead of creating an identifier that addresses things by location
- address it by some representation of the content itself
- if in your browser you want to access a particular page then IPFS will ask the entire network "does anyone have this file that corresponds to this hash?"
- instead of saying where to find a resource, you just say what it is you want
- instead of creating an identifier that addresses things by location
- called "location based addressing"
- is great for loading websites but it wasn’t designed for the transfer of large amounts data
- example: audio, video files
- files are downloaded from one server at a time
- IPFS retrieves pieces of files from multiple nodes at once
- enables bandwidth savings of up to 60% for things like videos
- IPFS retrieves pieces of files from multiple nodes at once
- enabled the emergence and mainstream success of alternative filesharing systems
- example: Napster (music) and BitTorrent (movies and pretty much anything)
- refers objects (text files, pics, videos) by which server they are stored on
- http
- example: ask someone for their favorite cat video
- mechanism
- take a file
- hash it cryptographically
- very small and secure representation of the file
- hashes are actually something called a multihash
- specifies which hash function it used
- specifies the length of the resultant hash in the first two bytes of the multihash
- example: hashes all seem to start with Qm
- 12 denotes that this is the SHA256
- output length is 20 in hex (or 32 bytes)
- we get the Qm from when we base58 encode the whole thing
- address usually starts with a hash that identifies some root object and then a path walking down
- instead of a server, you are talking to a specific object and then you are looking at a path within that object
- means that the content is going to determine the address
- content is linked via Merkle Directed Acyclic Graphs (DAGs)
- hashes are used to reference data blocks and objects in a DAG
- similar to Merkle tree
- difference: Merkle DAG non-leaf nodes are allowed to contain data
- need not be balanced
- example: https://explore.ipld.io/#/explore/QmWNj1pTSjbauDHpdyg5HQ26vYcNWnubg1JehmwAE9NnU9
- de-duplication by design
- we reference content (not the files themselves)
- adding a new file to the network will take less storage as the network gets larger
- assuming the file is relatively similar to others in the network
- content discovery system is facilitated via Distributed Hash Tables (DHTs)
- information about which node stores what blocks is organized as a distributed hash table
- split across the nodes just like data itself
- small values (equal to or less than 1KB) are stored directly on the DHT
- for larger values, the DHT stores references (NodeIds of peers who can serve the block)
- ask the network = query the distributed hash table
- based on Kademlia
- pretty common is p2p systems
- example: find peers that can provide a particular bit of content
ipfs dht findprovs QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ
- information about which node stores what blocks is organized as a distributed hash table
- unique identification via content addressing
- distributed file system that seeks to connect all computing devices with the same system of files
- small file (< 256 kB)
- represented by an IPFS object
- data = the file contents (plus a small header and footer)
- no links, i.e. the links array is empty
- represented by an IPFS object
- large file (> 256 kB)
- represented by an IPFS object
- data = specifying that this object represents a large file
- links = list of links to file chunks that are < 256 kB
- represented by an IPFS object
- directory is represented by a list of links to IPFS objects representing files or other directories
- IPFS object
- example
$ ipfs object get QmarHSr9aSNaPSR6G9KFPbuLV9aEqJfTk1y9B8pdwqK4Rq // normally referred to by their Base58 encoded hash { "Links": [ { "Name": "AnotherName", "Hash": "QmVtYjNij3KeyGmcgg7yVXWskLaBtov3UYL9pgcGK3MCWu", "Size": 18 }, { "Name": "SomeName", "Hash": "QmbUSy8HCn8J4TMDRRdxCbK2uCCtkQyZtY6XYv3y7kLgDC", "Size": 58 } ], "Data": "Hello World!" }
- Data - a blob of unstructured binary data of size < 256 kB
- Links - an array of Link structures (links to other IPFS objects)
- Name — the name of the Link
- Hash — the hash of the linked IPFS object
- Size — the cumulative size of the linked IPFS object, including following its links
- note that the file name is not part of the IPFS object
- two files with different names and the same content => same IPFS object representation
- and hence the same hash
- two files with different names and the same content => same IPFS object representation
- example
- small file (< 256 kB)
- is essentially a P2P system for retrieving and sharing IPFS objects
- acts as a decentralized source of data
- distributed: processing is shared across multiple nodes
- decisions may still be centralized and use complete system knowledge
- decentralized: no single point where the decision is made
- every node makes a decision for it’s own behaviour
- resulting system behaviour is the aggregate response
- distributed: processing is shared across multiple nodes
- self-certifying file system
- peer node can perform authentication and encryption
- when you initialize a new peer IPFS generates a pair of keys for it (private and public)
$ ipfs init initializing IPFS node at ~/.ipfs/ generating 2048-bit RSA keypair...done peer identity: Qm...
- when you initialize a new peer IPFS generates a pair of keys for it (private and public)
- peers are identifying each other via their peer ID
- is essentially a cryptographic hash of it’s public key
- enables peers to find each other and authenticate themselves once they get connected
- when two peers connect to each other they exchange public keys
- connections between peers are encrypted and authenticated by default
- authenticity of this data can be verified using the sender’s public key
- peer node can perform authentication and encryption
- operations
- retrieving
- when a node retrieves data from the network it keeps a local cache of that data for future usage
- nodes frequently clear this cache out in order to make room for new content
- HTTP -> IPFS gateway
- we can access any one of our files from their website
- https://ipfs.github.io/public-gateway-checker/
- example
- anyone can access your data provided that they know the hash
- use combination of symmetric and asymmetric encryption
- retrieving files from peer nodes and not some trusted centralized server
- trust: how can you be sure that the file you requested hasn’t been tampered with?
- since we are using a hash to request the file, you can verify what you received
- upon receiving a file, you can check if the hash of the file received matches the hash of the file requested
- any change to the file itself will change the hashed address
- when a node retrieves data from the network it keeps a local cache of that data for future usage
- adding
- each node chooses which file to store
- long-term IPFS storage: Filecoin
- each node chooses which file to store
- pinning
- act of saving data on a node
- prevents important data from being deleted from your node when the clearing process happens
- you can only control and pin data on your node(s)
- cannot force other nodes on the IPFS network to pin your content for you
- to guarantee your content stays pinned, you have to run your own IPFS nodes
- by pinning the file, other nodes on the network know they can access the file
- retrieving
- is similar to a single BitTorrent swarm exchanging git objects
- IPFS = one big swarm of peers for all data
- difference: in BitTorrent each file has a separate swarm of peers (forming a P2P network with each other)
- BitTorrent
- is a successful and widely implemented protocol used to share large data files in a distributed manner
- example: digital video sharing (TV shows, movies, video clips, etc)
- splits large data files into segments
- distributed over different nodes of a peer-to-peer network
- result: data is being shared from more than one source
- we are not exhausting a single server
- is a successful and widely implemented protocol used to share large data files in a distributed manner
- IPFS = one big swarm of peers for all data
- BitSwap
- their own exchange protocol
- data trading module for IPFS
- two primary jobs
- attempt to acquire blocks from the network that have been requested by the client peer (your local peer)
- judiciously (though strategically) send blocks of data that it already has in its possession to other peers who want those blocks
- example
- verify that wantlist is empty (we aren’t in the middle of requesting anything)
ipfs bitswap wantlist
- get a large file from the network
ipfs get QmdpAidwAsBGptFB3b6A9Pyi5coEbgjHrL3K2Qrsutmj9K // Big Buck Bunny video
- query wantlist again
ipfs bitswap wantlist // multiple hashes that are being requested from the network
- verify that wantlist is empty (we aren’t in the middle of requesting anything)
- consensus algorithm: Raft
- peers coordinate their state
- example: the list of CIDs which are pinned, their peer allocations and replication factor
- is used to commit log entries to a "distributed log" which every peer follows
- example: every "Pin" and "Unpin" requests are log entries in that log
- when a peer receives a log "Pin" operation, it updates its local copy of the shared state
- indicates that the CID is now pinned
- when a peer receives a log "Pin" operation, it updates its local copy of the shared state
- example: every "Pin" and "Unpin" requests are log entries in that log
- Leader
- election can only succeed if at least half of the nodes are online
- only peer allowed to commit entries to the log
- required for other parts of ipfs-cluster functionality (initialization, monitoring)
- peers coordinate their state
- blockchain
- problem: on the Ethereum platform you pay a rather large fee for storing data in the associated state database
- minimizes bloat of the state database ("blockchain bloat")
- solution: store not the data itself but hash of the data
- distinction between storing a hash on the blockchain and storing the data on the blockchain becomes somewhat blurred
- example: store an IPFS link in the blockchain
- we can seamlessly follow this link to access the data as if the data was stored in the blockchain itself
- problem: on the Ethereum platform you pay a rather large fee for storing data in the associated state database
- any asset that is digitally transferable between two people
- smart contract representing digital assets
- simple "databases" on the blockchain
- example
- ERC20: stores balances of Ethereum addresses
- ERC721: unique ID -> Ethereum address assignments
- example
- simple "databases" on the blockchain
- types
- fungible asset
- means that the individual units of an asset are interchangeable and essentially indistinguishable from one another
- not unique and divisable entity
- example: currency
- $50 is always $50
- non-fungible asset
- means that the individual units of an asset are distinct and unique
- often possessing specific attributes, characteristics, or properties that set them apart from one another
- cannot be exchanged on a one-to-one basis
- unique and indivisible entity
- examples
- education and certification
- Einstein diploma is not the same as Oppenheimer diploma
- diamonds
- aren’t interchangeable as they all have different cuts, colours and sizes
- can’t swap one diamond for another because they won’t be guaranteed to hold the same value
- education and certification
- means that the individual units of an asset are distinct and unique
- fungible asset
- history
- Bitcoin established the paradigm for other crypto projects: in order to issue any digital currency, a separate blockchain must be launched
- rule has been broken by Ethereum: smart contacts enabled to create a token and assign it unique useful functions within your own application
- ability for any developer to release their digital asset without the need for a separate blockchain has become a turning point in the history of cryptocurrencies
- coin
- native digital asset or cryptocurrency of a blockchain
- example:
- bitcoin blockchain => bitcoin (symbol: BTC)
- ethereum blockchain => ether (symbol: ETH)
- token
- digital asset or cryptocurrency that is built on top of an existing blockchain is called a token
- example (ERC20-compliant tokens built on the Ethereum blockchain)
- Maker (symbol: MKR)
- Augur (symbol: REP)
- Bitcoin established the paradigm for other crypto projects: in order to issue any digital currency, a separate blockchain must be launched
- problem: supporting an increasing number of tokens became increasingly difficult
- in order for the exchange or wallet to support the token, the creators had to write new code each time
- example: prior to ERC-20,there was a problem with token compatibility because each of them had a unique smart contract
- solution: standard protocol for all tokens (ERC)
- play a pivotal role in ensuring interoperability and compatibility among different smart contracts and decentralized applications (DApps)
- ERCs are analogous to RFCs
- Ethereum Improvement Proposals (EIPs): focused on the Ethereum protocol
- decentralization
- nearly half of the top 20 projects can have their token transfers completely frozen by an owner (a single key or a multisig contract)
- pausing can be valuable for future upgrades, swaps, and disaster mitigation but also leads to new risks
- nearly half of the top 20 projects can have their token transfers completely frozen by an owner (a single key or a multisig contract)
- example: Tether (USDT)
- value is pegged to the US dollar at a 1:1 ratio
- commonly used to move funds between exchanges quickly and easily
- use cases
- reputation points of any online platform
- lottery tickets and schemes
- financial assets such as shares, dividends, and stocks of a company
- fiat currencies, including USD
- gold ounce
- funglible
- six functions, two events, and three information
- functions
- totalSupply - returns the total supply of tokens that have been created for a particular project
- problem: Solidity and the Ethereum Virtual Machine do not support decimals: only integer numbers can be used
- solution: use larger integer values (the EVM supports 256-bit integers)
- example: total supply 1000 tokens, with 18 decimal places (like Ethereum) = 1000*10**18
_mint(msg.sender, 1000 * 10**18); // mining 1000 tokens transfer(recipient, 2 * 10**18); // sending 2 tokens
- example: total supply 1000 tokens, with 18 decimal places (like Ethereum) = 1000*10**18
- solution: use larger integer values (the EVM supports 256-bit integers)
- problem: Solidity and the Ethereum Virtual Machine do not support decimals: only integer numbers can be used
- balanceOf - returns the balance of tokens held by a particular address
- transfer - allows an address to send tokens to another address
- the only transaction that happens on the blockchain is the contract call
- transferring tokens from one account to another = simply update internal variable “_balances”
- problem: Solidity and the Ethereum Virtual Machine do not support decimals: only integer numbers can be used
- solution: token contract can use larger integer values (the EVM supports 256-bit integers)
- example: 1000000000000000000 represents 1 ETH (with 18 decimal places)
- transfer of 4000000000000000 will correspond to 0.004ETH being sent
- example: 1000000000000000000 represents 1 ETH (with 18 decimal places)
- solution: token contract can use larger integer values (the EVM supports 256-bit integers)
- token transfers are received at the contract silently
- smart contract does not explicitly notify or acknowledge the receipt of tokens
- solutions
- ERC223 standard provides
tokenFallback
method that will be called once the tokens are transferredfunction transfer(address to, uint value, bytes data) { uint codeLength; assembly { codeLength := extcodesize(_to) // user wallets do not have code associated with them } balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); if(codeLength>0) { // Require proper transaction handling. ERC223Receiver receiver = ERC223Receiver(_to); receiver.tokenFallback(msg.sender, _value, _data); } }
- approve plus transferFrom mechanism
- example
contract DEX { Erc20 public token; constructor(address _tokenAddress) { token = Erc20(_tokenAddress); } function tradeTokens(address _buyer, uint256 _amount) external { // Assume _amount is the number of tokens the buyer wants to purchase require(token.transferFrom(_buyer, address(this), _amount), "Token transfer failed"); // DEX now holds _amount of tokens // Perform trading logic here // ... } }
- example
- ERC223 standard provides
- approve - allows an address to approve another address to spend tokens on their behalf
- used by decentralized exchanges
- there are two types of exchanges
- centralized exchange
- example: Binance
- you have to send your cryptocurrency coin or token to the exchange's account
- then they allow you to trade on their platform
- trust issue: coins and tokens are held on an exchange's account
- if the exchange's account is hacked, you will lose your cryptocurrencies
- decentralized exchange
- example: Uniswap
- all coins/token are kept in your wallet only
- directly trade via exchange platform
- centralized exchange
- there are two types of exchanges
- front-running approval attack
- problem: you change your approve amount to a given contract
- example: reduce the amount approved from 1 ETH to 0.5 ETH
- approved contract can race to transfer the money you initially approved (the 1 ETH)
- and then also spend the money you just approved (0.5 ETH)
- approved contract can race to transfer the money you initially approved (the 1 ETH)
- example: reduce the amount approved from 1 ETH to 0.5 ETH
- solutions
- use the increaseApproval() or decreaseApproval() functions
- ensures that, before updating the value, it should be set to zero
require(_value == 0 || allowed[msg.sender][_spender] == 0);
- problem: you change your approve amount to a given contract
- used by decentralized exchanges
- transferFrom - allows an address to transfer tokens from another address that has approved them to do so
- prerequisite: approver must have called the approve() function prior
- if called from within a Solidity contract recommended to enclose with require
require(ERC20.transferFrom(from, to, value)) // in case of any transfer failure, the transaction should revert
- allowance - returns the amount of tokens that an approved address can spend on behalf of another address
- developers can also add additional functions and features
- example: OpenZeppelin implementation of ERC20 contracts
- _mint(), _burn(), _burnFrom(), etc
- example: OpenZeppelin implementation of ERC20 contracts
- totalSupply - returns the total supply of tokens that have been created for a particular project
- information
- name - returns the name of the token
- symbol - returns the symbol of the token
- usually a few letters or characters that represent the token
- decimals - returns the number of decimal places that the token can be divided into
- example: a token with 18 decimal places can be divided into 10^18 units
- event
- transfer(address indexed _from, address indexed _to, uint256 _value)
- triggered whenever tokens are transferred
- minting emits transfer event with the 0 address as the source
- approval(address indexed _owner, address indexed _spender, uint256 _value)
- triggered on any call to approve() function
- transfer(address indexed _from, address indexed _to, uint256 _value)
- functions
- example: domain name
- use cases
- unique digital content piece
- through royalty implementations in the smart contract, original creators can receive a predetermined percentage of sales whenever the NFT changes hands
- real estate property
- social media content Tweets, Videos, and pictures
- gaming assets and collectibles
- gaming characters
- unique digital content piece
- non-fungible
- each NFT has a numerical identifier (uint256) called TokenID
- is a digital receipt of something you purchased
- digital certificates of authenticity
- example: when you buy an NFT, you are buying a piece of data that points to a server that hosts that image
- so, what you own, is not the access to the server, and not the image itself, but rather that tiny piece of data that points to the server
- represents a class of assets, whereas an ERC20 token represents a particular type of asset
- entire collection comes from a single ERC-721 contract, with each item having its own TokenID
- smart contracts define the ownership and transfer rights of a particular digital asset
- case studies
- art
- artists don’t have to rely on gallery shows or live auctions to be able to sell their art
- monetize their projects without the physical resources needed to do that in the real world
- artists don’t have to rely on gallery shows or live auctions to be able to sell their art
- collectibles
- sports leagues including the NFL, MLB and NBA have all created digital collections
- gaming and virtual reality
- players will invest more when digital assets can be
- transferred between games or platforms
- traded on open markets, they will invest more of their hard-earned cash
- players will invest more when digital assets can be
- licenses and certifications
- easily verifiable with university smart contract
- art
- functions
- erc20 like
- balanceOf(address _owner)
- transferFrom(address _from, address _to, uint256 _tokenId)
- approve(address _approved, uint256 _tokenId)
- setApprovalForAll(address _operator, bool _approved)
- getApproved(uint256 _tokenId)
- isApprovedForAll(address _owner, address _operator)
- function ownerOf(uint256 _tokenId)
- function safeTransferFrom(address _from, address _to, uint256 _tokenId)
- ensure the destination address can handle the NFT, preventing accidental loss
- is used to check if the address receiving the token is an ERC-721 receiver or not
- ERC721TokenReceiver
- minimal acceptable feature for onERC721Received is to do nothing and this is how they have implemented it
- goal is to ensure that your NFT does not get locked up in an address from which you can never retrieve it
- ERC721TokenReceiver
- function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes _data)
- allows for additional data to be passed to the receiving contract
- example
then
{ // _data "rarity": "Rare", "strength": 80, "abilities": ["Flying", "Fire Breath"] }
contract MyCardRegistry { struct CardAttributes { string rarity; uint256 strength; string[] abilities; } mapping(uint256 => CardAttributes) public cardAttributes; function parseDataToCardAttributes(bytes calldata _data) internal pure returns (CardAttributes memory) { // Parse JSON data (string memory rarity, uint256 strength, string[] memory abilities) = abi.decode(_data, (string, uint256, string[])); return CardAttributes({ rarity: rarity, strength: strength, abilities: abilities }); } function onCardReceived(address _sender, uint256 _cardId, bytes calldata _data) external returns (bool) { // Parse the _data and update cardAttributes with the received attributes CardAttributes memory attributes = parseDataToCardAttributes(_data); // Add the card to the registry cardAttributes[_cardId] = attributes; return true; } }
- developers can also add additional functions and features
- example: OpenZeppelin implementation of ERC721 contracts
- tokenApprovals, operatorApprovals, etc
- example: OpenZeppelin implementation of ERC721 contracts
- erc20 like
- information
- erc20 like
- name()
- symbol()
- tokenURI(tokenId)
- points to the metadata
- allowing platforms and wallets to display the NFT’s distinct attributes
- problem: centralized nft
- returns the metadata location in the form of:
HTTP://centralizedserver.com/TokenID
- here is nothing stopping the image host from changing the image which the URL points to
- example: https://example.com/nft/1
{ "name": "One Ring to Rule Them All", "description": "The One Ring, forged in the fires of Mount Doom, grants immense power to its bearer.", "image": "https://example.com/one_ring.jpg", "attributes": [ { "trait_type": "Type", "value": "Artifact" }, { "trait_type": "Rarity", "value": "Legendary" }, { "trait_type": "Power", "value": "Dominion over all other rings" }, { "trait_type": "Owner", "value": "Sauron" } ], "external_url": "https://example.com/one_ring", "franchise": "The Lord of the Rings", "lore": "Forged by the Dark Lord Sauron to control the other Rings of Power, the One Ring is a malevolent artifact of great evil." }
- example: https://example.com/nft/1
- here is nothing stopping the image host from changing the image which the URL points to
- solution: IPFS
- problem: cannot set
ipfs://contendidentifierhash/TokenID
- IPFS can only provide an immutable hierarchical file system structure
- requirement: all tokens and TokenIDs are set at the time of the smart contract deployment
- solution: TokenID as a pointer itself to the metadata
TokenID=IPFScontentidentifierhash
- idea by Titusz Pan
- we would then just add "ipfs://" in front
- example
then convert it to hex “-b=base16”
$ ipfs add MetaDataIPFSToken.json cid-version=1 hash=blake2b-208 added bafkzvzacdkm3bu3t266ivacqjowxqi3hvpqsyijxhsb23rv7nj7a MetaDataIPFSToken.json
without f0 gives a token ID$ ipfs cid format -b=base16 bafkzvzacdkm3bu3t266ivacqjowxqi3hvpqsyijxhsb23rv7nj7a f01559ae4021a99b0d373d7bc8a80504bad782367abe12c21373c83adc6bf6a7e
constructor() public ERC1155("ipfs://f0{id}") { // "f0" is not part of the CID, but rather a representation of the format being used (base16)
- problem: cannot set
- returns the metadata location in the form of:
- erc20 like
- events
- erc20 like
- event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
- event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
- event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
- erc20 like
- OpenSea
- refers to itself as the "amazon of NFTs"
- has a large and wide variety of different types of NFTs
- before
- problem: game with 100,000 items = 100,000 smart contracts
- ERC-1155 developer Witek Radomski pointed out: that's like needing a different phone for each app you use
- solution: ERC1155
- allows for multiple token collections to be launched in just one smart contract
- problem: game with 100,000 items = 100,000 smart contracts
- called fungible-agnostic standard
- allows multiple token to be represented by a single contract, regardless of fungibility
- allows the creation of fungible, non-fungible, and semi-fungible tokens all in one contract
- semi-fungible tokens
- possesses characteristics of both fungible and non-fungible tokens
- certain units of the token may be interchangeable with other units
- others may have distinct characteristics, making them distinguishable from one another
- example: trading cards
- fungible: can still be traded within the same collection
- non-fungible: not completely interchangeable with each other
- possesses characteristics of both fungible and non-fungible tokens
- semi-fungible tokens
- makes use of TokenID
- each token ID to represent a new configurable token type, which may have its own metadata, supply and other attributes
- supply is only 1 <=> NFT
- each token ID to represent a new configurable token type, which may have its own metadata, supply and other attributes
- supports secure atomic swaps
- all tokens are inside one contract
- use case
- blockchain-based decentralized games, as games need coins and collectibles
- creating and managing digital art and collectible tokens with different levels of rarity, editions, and properties
- can save costs by managing tokens in batches (approval, transfer and balance) instead of individually
- functions
- safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes calldata _data)
- token transfers to other contracts may revert with the following message: ERC1155: transfer to non ERC1155Receiver implementer
- implementation: ERC165 - interface detection
- means that the recipient contract has not registered itself as aware of the ERC1155 protocol => transfers to it are disabled
- example: Golem contract
- currently holds over 350k GNT tokens (multiple tens of thousands of dollars) and lacks methods to get them out of there
- has happened to virtually every ERC20-backed project, usually due to user error
- currently holds over 350k GNT tokens (multiple tens of thousands of dollars) and lacks methods to get them out of there
- transfer call must revert if: _to address is 0
- token transfers to other contracts may revert with the following message: ERC1155: transfer to non ERC1155Receiver implementer
- safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data)
- any number of items can be sent in a single transaction to one or more recipients
- reducing transaction costs and complexity
- balanceOf(address _owner, uint256 _id)
- differs from ERC20: has an additional id argument for the identifier of the token that you want to query the balance of
- balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids)
- setApprovalForAll(address _operator, bool _approved)
- intentionally designed with simplicity in mind: can only approve everything for one address
- isApprovedForAll(address _owner, address _operator)
- safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes calldata _data)
- events
- TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value)
- TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values)
- ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)
- must implement all of the functions in the ERC1155TokenReceiver interface to accept transfers
- onERC1155Received
- onERC1155BatchReceived
- before
- determining a smart contract’s supported interfaces was a challenging and gas-consuming process
- some interaction might involve sending test transactions to the contract and observing the behavior
- if the contract didn't support the desired interface, the transaction would fail or return an unexpected result
- problem: interfaces
- are not explicitly represented on the blockchain
- used by developers during the coding and development process
- applications must usually simply trust they are not making an incorrect call
- contract declaring its interface can be very helpful in preventing errors
- solution: ERC165
- known as the Standard Interface Detection
- we can publish and detect all interfaces a smart contract implements
- accounts simply declare their interfaces
- they are not required to actually implement them
- must not be relied on for security
- example
- publish
contract MyContract is ERC165 { function supportsInterface(bytes4 interfaceID) external view override returns (bool) { return interfaceID == type(ERC165).interfaceId; // defined as the XOR of all function (it implements) selectors } }
- verify
function checkIfContractSupportsERC165(address contractAddress) external view returns (bool) { ERC165 erc165 = ERC165(contractAddress); return erc165.supportsInterface(INTERFACE_ID_ERC165); }
- publish
- accounts simply declare their interfaces
- use case
- play a vital role in enabling seamless interactions between smart contracts and DApps
- example: OpenSea - NFT Marketplace
- uses ERC165 to check if a given contract supports the ERC721 standard, which is the most common standard for non-fungible tokens
- if a contract doesn't support ERC721 (as indicated by the supportsInterface call), OpenSea will handle it differently
- example: OpenSea - NFT Marketplace
- play a vital role in enabling seamless interactions between smart contracts and DApps
- an open-source library of protocols, templates, & utilities for smart contract development
- includes implementations for token standards, flexible role-based permissioning schemes, & reusable components
- in particular: offers implementations for ERC20 , ERC721, & ERC1155