Developer Documentation
INTRODUCTION TO THE KINGLORY STACK
Like any software stack, the complete “Kinglory stack” will vary from project to project depending on your business goals.
There are, however, core technologies of Kinglory that help provide a mental model for how software applications interact with the Kinglory blockchain. Understanding the layers of the stack will help you understand the different ways that Kinglory can be integrated into software projects.
LEVEL 1: KINGLORY VIRTUAL MACHINE
Like any software stack, the complete “Kinglory stack” will vary from project to project depending on your business goals.
There are, however, core technologies of Kinglory that help provide a mental model for how software applications interact with the Kinglory blockchain. Understanding the layers of the stack will help you understand the different ways that Kinglory can be integrated into software projects.
LEVEL 2: SMART CONTRACTS
Smart contracts are the executable programs that run on the Kinglory blockchain.
Smart contracts are written using specific programming languages that compile to KVM bytecode (low-level machine instructions called opcodes).
Not only do smart contracts serve as open-source libraries, but they are also essentially open API services that run 24/7 and can’t be taken down. Smart contracts provide public functions which applications (DApps) may interact with, without needing permission. Any application may integrate with deployed smart contracts to compose functionality (such as data feeds or decentralized exchanges). Anyone can deploy new smart contracts to Kinglory to add custom functionality to meet their application’s needs.
As a DApp developer, you’ll need to write smart contracts only if you want to add custom functionality on the Kinglory blockchain. You may find you can achieve most or all your project’s needs by merely integrating with existing smart contracts, for instance if you want to support payments in stablecoins or enable decentralized exchange of tokens.
LEVEL 3: KINGLORY NODES
In order for an application to interact with the Kinglory blockchain (i.e. read blockchain data and/or send transactions to the network), it must connect to an Kinglory node.
Kinglory nodes are computers running software – an Kinglory client. A client is an implementation of Kinglory that verifies all transactions in each block, keeping the network secure and the data accurate. Kinglory nodes ARE the Kinglory blockchain. They collectively store the state of the Kinglory blockchain and reach consensus on transactions to mutate the blockchain state.
By connecting your application to an Kinglory node (via a JSON RPC spec), your application is able to read data from the blockchain (such as user account balances) as well as broadcast new transactions to the network (such as transfering KGC between user accounts or executing functions of smart contracts).
LEVEL 4: KINGLORY CLIENT APIS
Many convenience libraries (built and maintained by Kinglory’s open source community) allow your end user applications to connect to and communicate with the Kinglory blockchain.
If your user-facing application is a web app, you may choose to npm install a JavaScript API directly in your frontend. Or perhaps you’ll choose to implement this functionality server-side, using a Python or Java API.
While these APIs are not a necessary piece of the stack, they abstract away much of the complexity of interacting directly with an Kinglory node. They also provide utility functions (e.g. converting KGC to Glory) so as a developer you can spend less time dealing with the intricacies of Kinglory clients and more time focused on the unique functionality of your application.
LEVEL 5: END USER APPLICATIONS
At the top level of the stack are user-facing applications. These are the standard applications you regularly use and build today: primarily web and mobile apps.
The way you develop these user interfaces remains essentially unchanged. Often users will not need to know the application they’re using is built using a blockchain.
READY TO CHOOSE YOUR STACK?
Check out our guide to set up a local development environment for your Kinglory application.
INTRODUCTION TO SMART CONTRACTS
WHAT IS A SMART CONTRACT?
A “smart contract” is simply a program that runs on the Kinglory blockchain. It’s a collection of code (its functions) and data (its state) that resides at a specific address on the Kinglory blockchain.
Smart contracts are a type of Kinglory account. This means they have a balance and they can send transactions over the network. However they’re not controlled by a user, instead they are deployed to the network and run as programmed. User accounts can then interact with a smart contract by submitting transactions that execute a function defined on the smart contract. Smart contracts can define rules, like a regular contract, and automatically enforce them via the code.
PREREQUISITES
Make sure you’ve read up on accounts, transactions and the Kinglory virtual machine before jumping into the world of smart contracts.
A DIGITAL VENDING MACHINE
pragma solidity 0.6.11; contract VendingMachine { // Declare state variables of the contract address public owner; mapping (address => uint) public cupcakeBalances; // When 'VendingMachine' contract is deployed: // 1. set the deploying address as the owner of the contract // 2. set the deployed smart contract's cupcake balance to 100 constructor() public { owner = msg.sender; cupcakeBalances[address(this)] = 100; } // Allow the owner to increase the smart contract's cupcake balance function refill(uint amount) public { require(msg.sender == owner, "Only the owner can refill."); cupcakeBalances[address(this)] += amount; } // Allow anyone to purchase cupcakes function purchase(uint amount) public payable { require(msg.value >= amount * 1 KGC, "You must pay at least 1 KGC per cupcake"); require(cupcakeBalances[address(this)] >= amount, "Not enough cupcakes in stock to complete this purchase"); cupcakeBalances[address(this)] -= amount; cupcakeBalances[msg.sender] += amount; } }Like how a vending machine removes the need for a vendor employee, smart contracts can replace intermediaries in many industries.
PERMISSIONLESS
Anyone can write a smart contract and deploy it to the network. You just need to learn how to code in a smart contract language, and have enough KGC to deploy your contract. Deploying a smart contract is technically a transaction, so you need to pay your Gas in the same way that you need to pay gas for a simple KGC transfer. Gas costs for contract deployment are far higher, however.
Kinglory has developer-friendly languages for writing smart contracts:
- Solidity
- Vyper
More on languages
However, they must be compiled before they can be deployed so that Kinglory’s virtual machine can interpret and store the contract. More on compilation
COMPOSABILITY
Smart contracts are public on Kinglory and can be thought of as open APIs. That means you can call other smart contracts in your own smart contract to greatly extend what’s possible. Contracts can even deploy other contracts.
Learn more about smart contract composability.
LIMITATIONS
Smart contracts alone cannot get information about “real-world” events because they can’t send HTTP requests. This is by design. Relying on external information could jeopardise consensus, which is important for security and decentralization.
There are ways to get around this using oracles.
SMART CONTRACT LANGUAGES
A great aspect about Kinglory is that smart contracts can be programmed using relatively developer-friendly languages. If you’re experienced with Python or JavaScript, you can find a language with familiar syntax.
The two most active and maintained languages are:
- Solidity
- Vyper
More experienced developers also might want to use Yul, an intermediate language for the Kinglory Virtual Machine, or Yul+, an extension to Yul.
PREREQUISITES
Previous knowledge of programming languages, especially of JavaScript or Python, can help you make sense of differences in smart contract languages. We also recommend you understand smart contracts as a concept before digging too deep into the language comparisons. Intro to smart contracts.
SOLIDITY
Important links Documentation Solidity Language Portal Solidity by Example GitHub Solidity Gitter Chatroom Cheat Sheet Solidity Blog
Example contract
// SPDX-License-Identifier: GPL-3.0 pragma solidity >= 0.7.0; contract Coin { // The keyword "public" makes variables // accessible from other contracts address public minter; mapping (address => uint) public balances; // Events allow clients to react to specific // contract changes you declare event Sent(address from, address to, uint amount); // Constructor code is only run when the contract // is created constructor() { minter = msg.sender; } // Sends an amount of newly created coins to an address // Can only be called by the contract creator function mint(address receiver, uint amount) public { require(msg.sender == minter); require(amount < 1e60); balances[receiver] += amount; } // Sends an amount of existing coins // from any caller to an address function send(address receiver, uint amount) public { require(amount <= balances[msg.sender], "Insufficient balance."); balances[msg.sender] -= amount; balances[receiver] += amount; emit Sent(msg.sender, receiver, amount); } }This example should give you a sense of what Solidity contract syntax is like. For a more detailed description of the functions and variables, see the docs.
VYPER
- Modifiers
- Inheritance
- Inline assembly
- Function overloading
- Operator overloading
- Recursive calling
- Infinite-length loops
- Binary fixed points
Important links
Documentation
Vyper by Example
GitHub
Vyper Gitter Chatroom
Cheat Sheet
Example
# Open Auction # Auction params # Beneficiary receives money from the highest bidder beneficiary: public(address) auctionStart: public(uint256) auctionEnd: public(uint256) # Current state of auction highestBidder: public(address) highestBid: public(uint256) # Set to true at the end, disallows any change ended: public(bool) # Keep track of refunded bids so we can follow the withdraw pattern pendingReturns: public(HashMap[address, uint256]) # Create a simple auction with `_bidding_time` # seconds bidding time on behalf of the # beneficiary address `_beneficiary`. @external def __init__(_beneficiary: address, _bidding_time: uint256): self.beneficiary = _beneficiary self.auctionStart = block.timestamp self.auctionEnd = self.auctionStart + _bidding_time # Bid on the auction with the value sent # together with this transaction. # The value will only be refunded if the # auction is not won. @external @payable def bid(): # Check if bidding period is over. assert block.timestamp < self.auctionEnd # Check if bid is high enough assert msg.value > self.highestBid # Track the refund for the previous high bidder self.pendingReturns[self.highestBidder] += self.highestBid # Track new high bid self.highestBidder = msg.sender self.highestBid = msg.value # Withdraw a previously refunded bid. The withdraw pattern is # used here to avoid a security issue. If refunds were directly # sent as part of bid(), a malicious bidding contract could block # those refunds and thus block new higher bids from coming in. @external def withdraw(): pending_amount: uint256 = self.pendingReturns[msg.sender] self.pendingReturns[msg.sender] = 0 send(msg.sender, pending_amount) # End the auction and send the highest bid # to the beneficiary. @external def endAuction(): # It is a good guideline to structure functions that interact # with other contracts (i.e. they call functions or send KGC) # into three phases: # 1. checking conditions # 2. performing actions (potentially changing conditions) # 3. interacting with other contracts # If these phases are mixed up, the other contract could call # back into the current contract and modify the state or cause # effects (KGC payout) to be performed multiple times. # If functions called internally include interaction with external # contracts, they also have to be considered interaction with # external contracts. # 1. Conditions # Check if auction endtime has been reached assert block.timestamp >= self.auctionEnd # Check if this function has already been called assert not self.ended # 2. Effects self.ended = True # 3. Interaction send(self.beneficiary, self.highestBid)This example should give you a sense of what Vyper contract syntax is like. For a more detailed description of the functions and variables, see the docs.
YUL AND YUL+
Yul
Intermediate language for Kinglory. Supports the KVM and Ewasm, an Kinglory flavored WebAssembly, and is designed to be a usable common denominator of both platforms. Good target for high-level optimisation stages that can benefit both KVM and Ewasm platforms equally.
Yul+
A low-level, highly efficient extension to Yul. Initially designed for an optimistic rollup contract. Yul+ can be looked at as an experimental upgrade proposal to Yul, adding new features to it.
Important links
Yul Documentation
Yul+ Documentation
Yul+ Playground
Yul+ Introduction Post
Example contract
The following simple example implements a power function. It can be compiled using solc --strict-assembly --bin input.yul. The example should be stored in the input.yul file. { function power(base, exponent) -> result { switch exponent case 0 { result := 1 } case 1 { result := base } default { result := power(mul(base, base), div(exponent, 2)) if mod(exponent, 2) { result := mul(base, result) } } } let res := power(calldataload(0), calldataload(32)) mstore(0, res) return(0, 32) }If you are already well experienced with smart contracts, a full ERC20 implementation in Yul can be found here.
HOW TO CHOOSE
As with any other programming language, it’s mostly about choosing the right tool for the right job as well as personal preferences.
Here are a few things to consider if you haven’t tried any of the languages yet:
What is great about Solidity?
If you are a beginner, there are many tutorials and learning tools out there. See more about that in the Learn by Coding section.
Good developer tooling available.
Solidity has a big developer community, which means you’ll most likely find answers to your questions quite quickly.
What is great about Vyper?
Great way to get started for Python devs that want to write smart contracts.
Vyper has a smaller number of features which makes it great for quick prototyping of ideas.
Vyper aims to be easy to audit and maximally human-readable.
What is great about Yul and Yul+?
Simplistic and functional low-level language.
Allows to get much closer to raw KVM, which can help to optimize the gas usage of your contracts.
LANGUAGE COMPARISONS
For comparisons of basic syntax, the contract lifecycle, interfaces, operators, data structures, functions, control flow, and more check out this cheatsheet by Auditless
ANATOMY OF SMART CONTRACTS
A smart contract is a program that runs at an address on Kinglory. They’re made up of data and functions that can execute upon receiving a transaction. Here’s an overview of what makes up a smart contract.
Prerequisites
Make sure you’ve read about smart contracts first. This document assumes you’re already familiar with programming languages such as JavaScript or Python.
DATA
Any contract data must be assigned to a location: either to storage or memory. It’s costly to modify storage in a smart contract so you need to consider where your data should live.
Storage
Persistent data is referred to as storage and is represented by state variables. These values get stored permanently on the blockchain. You need to declare the type so that the contract can keep track of how much storage on the blockchain it needs when it compiles.
// Solidity example contract SimpleStorage { uint storedData; // State variable // ... } # Vyper example storedData: int128
If you’ve already programmed object-oriented languages, you’ll likely be familiar with most types. However address should be new to you if you’re new to Kinglory development.
An address type can hold an Kinglory address which equates to 20 bytes or 160 bits. It returns in hexadecimal notation with a leading 0x.
Other types include:
- boolean
- integer
- fixed point numbers
- fixed-size byte arrays
- dynamically-sized byte
- arrays
- Rational and integer literals
- String literals
- Hexadecimal literals
- Enums
For more explanation, take a look at the docs:
See Vyper types
See Solidity types
Memory
Values that are only stored for the lifetime of a contract function’s execution are called memory variables. Since these are not stored permanently on the blockchain, they are much cheaper to use.
Learn more about how the KVM stores data (Storage, Memory, and the Stack) in the Solidity docs.
Environment variables
block.timestamp uint256
msg.sender
msg.sender
Description
Current block epoch timestamp
Sender of the message (current call)
FUNCTIONS
- internal – these don’t create an KVM call
- Internal functions and state variables can only be accessed internally (i.e. from within the current contract or contracts deriving from it)
- external – these do create an KVM call
- External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f() does not work, but this.f() works).
- public functions can be called internally from within the contract or externally via messages
- private functions are only visible for the contract they are defined in and not in derived contracts
- Both functions and state variables can be made public or private
// Solidity example function update_name(string value) public { dapp_name = value; }The parameter value of type string is passed into the function: update_name It’s declared public, meaning anyone can access it It’s not declared view, so it can modify the contract state
View functions
These functions promise not to modify the state of the contract’s data. Command examples are “getter” functions – you might use this to receive a user’s balance for example.
// Solidity example function balanceOf(address _owner) public view returns (uint256 _balance) { return ownerPizzaCount[_owner]; } dappName: public(string) @view @public def readName() -> string: return dappName
What is considered modifying state:
- Writing to state variables.
- Emitting events.
- Creating other contracts.
- Using selfdestruct.
- Sending KGC via calls.
- Calling any function not marked view or pure.
- Using low-level calls.
- Using inline assembly that contains certain opcodes.
Constructor functions
Constructor functions are only executed once when the contract is first deployed. Like constructor in many class-based programming languages, these functions often initialize state variables to their specified values.
// Solidity example // Initializes the contract's data, setting the `owner` // to the address of the contract creator. constructor() public { // All smart contracts rely on external transactions to trigger its functions. // `msg` is a global variable that includes relevant data on the given transaction, // such as the address of the sender and the KGC value included in the transaction. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties owner = msg.sender; } Show all # Vyper example @external def __init__(_beneficiary: address, _bidding_time: uint256): self.beneficiary = _beneficiary self.auctionStart = block.timestamp self.auctionEnd = self.auctionStart + _bidding_time
Built-in functions
In addition to the variables and functions you define on your contract, there are some special built-in functions. The most obvious example is:
- address.send() – Solidity
- send(address) – Vyper
These allow contracts to send KGC to other accounts.
WRITING FUNCTIONS
Your function needs:
- Parameter variable and type (if it accepts parameters)
- Declaration of internal/external
- Declaration of pure/view/payable
- Returns type (if it returns a value)
pragma solidity >=0.4.0 <=0.6.0; contract ExampleDapp { string dapp_name; // state variable // Called when the contract is deployed and initializes the value constructor() public { dapp_name = "My Example dapp"; } // Get Function function read_name() public view returns(string) { return dapp_name; } // Set Function function update_name(string value) public { dapp_name = value; } }
A complete contract might look something like this. Here the constructor function provides an initial value for the dapp_name variable.
EVENTS AND LOGS
Events let you communicate with your smart contract from your frontend or other subscribing applications. When a transaction is mined, smart contracts can emit events and write logs to the blockchain that the frontend can then process.
ANNOTATED EXAMPLES
These are some examples written in Solidity. If you’d like to play with the code, you can interact with them in Remix.
Hello world
// Specifies the version of Solidity, using semantic versioning. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma pragma solidity ^0.5.10; // Defines a contract named `HelloWorld`. // A contract is a collection of functions and data (its state). // Once deployed, a contract resides at a specific address on the Kinglory blockchain. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html contract HelloWorld { // Declares a state variable `message` of type `string`. // State variables are variables whose values are permanently stored in contract storage. // The keyword `public` makes variables accessible from outside a contract // and creates a function that other contracts or clients can call to access the value. string public message; // Similar to many class-based object-oriented languages, a constructor is // a special function that is only executed upon contract creation. // Constructors are used to initialize the contract's data. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors constructor(string memory initMessage) public { // Accepts a string argument `initMessage` and sets the value // into the contract's `message` storage variable). message = initMessage; } // A public function that accepts a string argument // and updates the `message` storage variable. function update(string memory newMessage) public { message = newMessage; } }
Token
pragma solidity ^0.5.10; contract Token { // An `address` is comparable to an email address - it's used to identify an account on Kinglory. // Addresses can represent a smart contract or an external (user) accounts. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#address address public owner; // A `mapping` is essentially a hash table data structure. // This `mapping` assigns an unsigned integer (the token balance) to an address (the token holder). // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types mapping (address => uint) public balances; // Events allow for logging of activity on the blockchain. // Kinglory clients can listen for events in order to react to contract state changes. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events event Transfer(address from, address to, uint amount); // Initializes the contract's data, setting the `owner` // to the address of the contract creator. constructor() public { // All smart contracts rely on external transactions to trigger its functions. // `msg` is a global variable that includes relevant data on the given transaction, // such as the address of the sender and the KGC value included in the transaction. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties owner = msg.sender; } // Creates an amount of new tokens and sends them to an address. function mint(address receiver, uint amount) public { // `require` is a control structure used to enforce certain conditions. // If a `require` statement evaluates to `false`, an exception is triggered, // which reverts all changes made to the state during the current call. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions // Only the contract owner can call this function require(msg.sender == owner, "You are not the owner."); // Ensures a maximum amount of tokens require(amount < 1e60, "Maximum issuance succeeded"); // Increases the balance of `receiver` by `amount` balances[receiver] += amount; } // Sends an amount of existing tokens from any caller to an address. function transfer(address receiver, uint amount) public { // The sender must have enough tokens to send require(amount <= balances[msg.sender], "Insufficient balance."); // Adjusts token balances of the two addresses balances[msg.sender] -= amount; balances[receiver] += amount; // Emits the event defined earlier emit Transfer(msg.sender, receiver, amount); } }
Unique digital asset
pragma solidity ^0.5.10; // Imports symbols from other files into the current contract. // In this case, a series of helper contracts from OpenZeppelin. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol"; import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol"; // The `is` keyword is used to inherit functions and keywords from external contracts. // In this case, `CryptoPizza` inherits from the `IERC721` and `ERC165` contracts. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance contract CryptoPizza is IERC721, ERC165 { // Uses OpenZeppelin's SafeMath library to perform arithmetic operations safely. // Learn more: https://docs.openzeppelin.com/contracts/2.x/api/math#SafeMath using SafeMath for uint256; // Constant state variables in Solidity are similar to other languages // but you must assign from an expression which is constant at compile time. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constant-state-variables uint256 constant dnaDigits = 10; uint256 constant dnaModulus = 10 ** dnaDigits; bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; // Struct types let you define your own type // Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs struct Pizza { string name; uint256 dna; } // Creates an empty array of Pizza structs Pizza[] public pizzas; // Mapping from pizza ID to its owner's address mapping(uint256 => address) public pizzaToOwner; // Mapping from owner's address to number of owned token mapping(address => uint256) public ownerPizzaCount; // Mapping from token ID to approved address mapping(uint256 => address) pizzaApprovals; // You can nest mappings, this example maps owner to operator approvals mapping(address => mapping(address => bool)) private operatorApprovals; // Internal function to create a random Pizza from string (name) and DNA function _createPizza(string memory _name, uint256 _dna) // The `internal` keyword means this function is only visible // within this contract and contracts that derive this contract // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters internal // `isUnique` is a function modifier that checks if the pizza already exists // Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers isUnique(_name, _dna) { // Adds Pizza to array of Pizzas and get id uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)), 1); // Checks that Pizza owner is the same as current user // Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions assert(pizzaToOwner[id] == address(0)); // Maps the Pizza to the owner pizzaToOwner[id] = msg.sender; ownerPizzaCount[msg.sender] = SafeMath.add( ownerPizzaCount[msg.sender], 1 ); } // Creates a random Pizza from string (name) function createRandomPizza(string memory _name) public { uint256 randDna = generateRandomDna(_name, msg.sender); _createPizza(_name, randDna); } // Generates random DNA from string (name) and address of the owner (creator) function generateRandomDna(string memory _str, address _owner) public // Functions marked as `pure` promise not to read from or modify the state // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions pure returns (uint256) { // Generates random uint from string (name) + address (owner) uint256 rand = uint256(keccak256(abi.encodePacked(_str))) + uint256(_owner); rand = rand % dnaModulus; return rand; } // Returns array of Pizzas found by owner function getPizzasByOwner(address _owner) public // Functions marked as `view` promise not to modify state // Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions view returns (uint256[] memory) { // Uses the `memory` storage location to store values only for the // lifecycle of this function call. // Learn more: https://solidity.readthedocs.io/en/v0.5.10/introduction-to-smart-contracts.html#storage-memory-and-the-stack uint256[] memory result = new uint256[](ownerPizzaCount[_owner]); uint256 counter = 0; for (uint256 i = 0; i < pizzas.length; i++) { if (pizzaToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } // Transfers Pizza and ownership to other address function transferFrom(address _from, address _to, uint256 _pizzaId) public { require(_from != address(0) && _to != address(0), "Invalid address."); require(_exists(_pizzaId), "Pizza does not exist."); require(_from != _to, "Cannot transfer to the same address."); require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved."); ownerPizzaCount[_to] = SafeMath.add(ownerPizzaCount[_to], 1); ownerPizzaCount[_from] = SafeMath.sub(ownerPizzaCount[_from], 1); pizzaToOwner[_pizzaId] = _to; // Emits event defined in the imported IERC721 contract emit Transfer(_from, _to, _pizzaId); _clearApproval(_to, _pizzaId); } /** * Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; * otherwise, the transfer is reverted. */ function safeTransferFrom(address from, address to, uint256 pizzaId) public { // solium-disable-next-line arg-overflow this.safeTransferFrom(from, to, pizzaId, ""); } /** * Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; * otherwise, the transfer is reverted. */ function safeTransferFrom( address from, address to, uint256 pizzaId, bytes memory _data ) public { this.transferFrom(from, to, pizzaId); require(_checkOnERC721Received(from, to, pizzaId, _data), "Must implmement onERC721Received."); } /** * Internal function to invoke `onERC721Received` on a target address * The call is not executed if the target address is not a contract */ function _checkOnERC721Received( address from, address to, uint256 pizzaId, bytes memory _data ) internal returns (bool) { if (!isContract(to)) { return true; } bytes4 retval = IERC721Receiver(to).onERC721Received( msg.sender, from, pizzaId, _data ); return (retval == _ERC721_RECEIVED); } // Burns a Pizza - destroys Token completely // The `external` function modifier means this function is // part of the contract interface and other contracts can call it function burn(uint256 _pizzaId) external { require(msg.sender != address(0), "Invalid address."); require(_exists(_pizzaId), "Pizza does not exist."); require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved."); ownerPizzaCount[msg.sender] = SafeMath.sub( ownerPizzaCount[msg.sender], 1 ); pizzaToOwner[_pizzaId] = address(0); } // Returns count of Pizzas by address function balanceOf(address _owner) public view returns (uint256 _balance) { return ownerPizzaCount[_owner]; } // Returns owner of the Pizza found by id function ownerOf(uint256 _pizzaId) public view returns (address _owner) { address owner = pizzaToOwner[_pizzaId]; require(owner != address(0), "Invalid Pizza ID."); return owner; } // Approves other address to transfer ownership of Pizza function approve(address _to, uint256 _pizzaId) public { require(msg.sender == pizzaToOwner[_pizzaId], "Must be the Pizza owner."); pizzaApprovals[_pizzaId] = _to; emit Approval(msg.sender, _to, _pizzaId); } // Returns approved address for specific Pizza function getApproved(uint256 _pizzaId) public view returns (address operator) { require(_exists(_pizzaId), "Pizza does not exist."); return pizzaApprovals[_pizzaId]; } /** * Private function to clear current approval of a given token ID * Reverts if the given address is not indeed the owner of the token */ function _clearApproval(address owner, uint256 _pizzaId) private { require(pizzaToOwner[_pizzaId] == owner, "Must be pizza owner."); require(_exists(_pizzaId), "Pizza does not exist."); if (pizzaApprovals[_pizzaId] != address(0)) { pizzaApprovals[_pizzaId] = address(0); } } /* * Sets or unsets the approval of a given operator * An operator is allowed to transfer all tokens of the sender on their behalf */ function setApprovalForAll(address to, bool approved) public { require(to != msg.sender, "Cannot approve own address"); operatorApprovals[msg.sender][to] = approved; emit ApprovalForAll(msg.sender, to, approved); } // Tells whether an operator is approved by a given owner function isApprovedForAll(address owner, address operator) public view returns (bool) { return operatorApprovals[owner][operator]; } // Takes ownership of Pizza - only for approved users function takeOwnership(uint256 _pizzaId) public { require(_isApprovedOrOwner(msg.sender, _pizzaId), "Address is not approved."); address owner = this.ownerOf(_pizzaId); this.transferFrom(owner, msg.sender, _pizzaId); } // Checks if Pizza exists function _exists(uint256 pizzaId) internal view returns (bool) { address owner = pizzaToOwner[pizzaId]; return owner != address(0); } // Checks if address is owner or is approved to transfer Pizza function _isApprovedOrOwner(address spender, uint256 pizzaId) internal view returns (bool) { address owner = pizzaToOwner[pizzaId]; // Disable solium check because of // https://github.com/duaraghav8/Solium/issues/175 // solium-disable-next-line operator-whitespace return (spender == owner || this.getApproved(pizzaId) == spender || this.isApprovedForAll(owner, spender)); } // Check if Pizza is unique and doesn't exist yet modifier isUnique(string memory _name, uint256 _dna) { bool result = true; for (uint256 i = 0; i < pizzas.length; i++) { if ( keccak256(abi.encodePacked(pizzas[i].name)) == keccak256(abi.encodePacked(_name)) && pizzas[i].dna == _dna ) { result = false; } } require(result, "Pizza with such name already exists."); _; } // Returns whether the target address is a contract function isContract(address account) internal view returns (bool) { uint256 size; // Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solium-disable-next-line security/no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } }
SMART CONTRACT LIBRARIES
You don’t need to write every smart contract in your project from scratch. There are many open source smart contract libraries available that provide reusable building blocks for your project that can save you from having to reinvent the wheel.
PREREQUISITES
Before jumping into smart contract libraries, it’s a good idea to have a nice understanding of the structure of a smart contract. Head over to smart contract anatomy if you haven’t done so yet.
WHAT'S IN A LIBRARY
You can usually find two kind of building blocks in smart contract libraries: reusable behaviors you can add to your contracts, and implementations of various standards.
Unique digital asset
contract Ownable { address public owner; constructor() internal { owner = msg.sender; } modifier onlyOwner() { require(owner == msg.sender, "Ownable: caller is not the owner"); _; } }To use a building block like this in your contract, you would need to first import it, and then extend from it in your own contracts. This will allow you to use the modifier provided by the base Ownable contract to secure your own functions.
import ".../Ownable.sol"; // Path to the imported library contract MyContract is Ownable { // The following function can only be called by the owner function secured() onlyOwner public { msg.sender.transfer(1 KGC); } }Another popular example is SafeMath or DsMath. These are libraries (as opposed to base contracts) that provide arithmetic functions with overflow checks, which are not provided by the language. It’s a good practice to use either of these libraries instead of native arithmetic operations to guard your contract against overflows, which can have disastrous consequences!
Standards
To facilitate composability and interoperability, the Kinglory community has defined several standards in the form of ERCs. You can read more about them in the standards section.
When including an ERC as part of your contracts, it’s a good idea to look for standard implementations rather than trying to roll out your own. Many smart contract libraries include implementations for the most popular ERCs. For example, the ubiquitous ERC20 fungible token standard can be found in HQ20, DappSys and OpenZeppelin. Additionally, some ERCs also provide canonical implementations as part of the ERC itself.
It’s worth mentioning that some ERCs are not standalone, but are additions to other ERCs. For example, ERC2612 adds an extension to ERC20 for improving its usability.
HOW TO ADD A LIBRARY
Always refer to the documentation of the library you are including for specific instructions on how to include it in your project. Several Solidity contract libraries are packaged using npm, so you can just npm install them. Most tools for compiling contracts will look into your node_modules for smart contract libraries, so you can do the following:
// This will load the @openzeppelin/contracts library from your node_modules import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MyNFT is ERC721 { constructor() ERC721("MyNFT", "MNFT") public { } }
Regardless of the method you use, when including a library, always keep an eye on the language version. For instance, you cannot use a library for Solidity 0.6 if you are writing your contracts in Solidity 0.5.
WHEN TO USE
Using a smart contract library for your project has several benefits. First and foremost, it saves you time by providing you with ready-to-use building blocks you can include in your system, rather than having to code them yourself.
Security is also a major plus. Open source smart contract libraries are also often heavily scrutinized. Given many projects depend on them, there is a strong incentive by the community to keep them under constant review. It’s much more common to find errors in application code than in reusable contract libraries. Some libraries also undergo external audits for additional security.
However, using smart contract libraries carry the risk of including code you are not familiar with into your project. It’s tempting to import a contract and include it directly into your project, but without a good understanding of what that contract does, you may be inadvertently introducing an issue in your system due to an unexpected behavior. Always make sure to read the documentation of the code you are importing, and then review the code itself before making it a part of your project!
Last, when deciding on whether to include a library, consider its overall usage. A widely-adopted one has the benefits of having a larger community and more eyes looking into it for issues. Security should be your primary focus when building with smart contracts!
TESTING SMART CONTRACTS
TESTING TOOLS AND LIBRARIES
Waffle – A framework for advanced smart contract development and testing.
getwaffle.io
GitHub
Solidity-Coverage – Alternative solidity code coverage tool.
GitHub
hevm – Implementation of the KVM made specifically for unit testing and debugging smart contracts.
GitHub
DappHub Chat
Whiteblock Genesis – An end-to-end development sandbox and testing platform for blockchain.
Whiteblock.io
Documentation
GitHub
OpenZeppelin Test Environment – Blazing fast smart contract testing. One-line setup for an awesome testing experience.
GitHub
Documentation
OpenZeppelin Test Helpers – Assertion library for Kinglory smart contract testing. Make sure your contracts behave as expected!
GitHub
Documentation
COMPILING SMART CONTRACTS
You need to compile your contract so that your web app and the Kinglory virtual machine (KVM) can understand it.
PREREQUISITES
You might find it helpful to have read our intro to smart contracts and the Kinglory virtual machine before reading about compilation.
THE KVM
pragma solidity 0.4.24; contract Greeter { function greet() public constant returns (string) { return "Hello"; } } into this PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0x46 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x52 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5B PUSH2 0xD6 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x9B JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x80 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0xC8 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x40 DUP1 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x5 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x48656C6C6F000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP SWAP1 POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLT 0xec 0xe 0xf5 0xf8 SLT 0xc7 0x2d STATICCALL ADDRESS SHR 0xdb COINBASE 0xb1 BALANCE 0xe8 0xf8 DUP14 0xda 0xad DUP13 LOG1 0x4c 0xb4 0x26 0xc2 DELEGATECALL PUSH7 0x8994D3E002900
WEB APPLICATIONS
[ { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } ]
DEPLOYING SMART CONTRACTS
You need to deploy your smart contract in order for it to be available to users of an Kinglory network.
To deploy a smart contract, you merely send an Kinglory transaction containing the code of the compiled smart contract without specifying any recipients.
PREREQUISITES
You should understand Kinglory networks, transactions and the anatomy of smart contracts before deploying smart contracts.
Deploying a contract also costs KGC, so you should be familiar with gas and fees on Kinglory.
Finally, you’ll need to compile your contract before deploying it, so make sure you’ve read about compiling smart contracts.
HOW TO DEPLOY A SMART CONTRACT
This means you’ll need to pay a transaction fee so make sure you have some KGC.
What you’ll need
Your contract’s bytecode – this is generated through compilation.
KGC for gas – you’ll set your gas limit like other transactions so be aware that contract deployment needs a lot more gas than a simple KGC transfer.
A deployment script or plugin.
Access to an Kinglory node, either by running your own, connecting to a public node, or via an API key using a service like Infura or Alchemy.
Once deployed, your contract will have an Kinglory address like other accounts.
RELATED TOOLS
Remix – Remix IDE allows developing, deploying and administering smart contracts for Kinglory like blockchains.
Remix
Tenderly – A platform to easily monitor your smart contracts with error tracking, alerting, performance metrics, and detailed contract analytics.
tenderly.co
GitHub
Discord
SMART CONTRACT COMPOSABILITY
A BRIEF INTRODUCTION
Smart contracts are public on Kinglory and can be thought of as open APIs. You don’t need to write your own smart contract to become a DApp developer, you just need to know how to interact with them. For example, you can use the existing smart contracts of Uniswap, a decentralized exchange, to handle all the token swap logic in your app – you don’t need to start from scratch. Check out some of their contracts.
DEVELOPMENT NETWORKS
When building an Kinglory application with smart contracts, you’ll want to run it on a local network to see how it works before deploying it.
Similar to how you might run a local server on your computer for web development, you can use a development network to create a local blockchain instance to test your DApp. These Kinglory development networks provide features that allow for much faster iteration than a public testnet (for instance you don’t need to deal with acquiring KGC from a testnet faucet).
PREREQUISITES
You should understand the basics of the Kinglory stack and Kinglory networks before diving into development networks.
WHAT IS A DEVELOPMENT NETWORK?
Development networks are essentially Kinglory clients (implementations of Kinglory) designed specifically for local development.
Why not just run a standard Kinglory node locally?
You could run a node but since development networks are purpose-built for development, they often come packed with convenient features like:
Deterministically seeding your local blockchain with data (e.g. accounts with KGC balances)
Instantly mining blocks with each transaction it receives, in order and with no delay
Enhanced debugging and logging functionality
AVAILABLE TOOLS
Note: Most development frameworks include a built-in development network. We recommend starting with a framework to set up your local development environment.
TestNet
Quickly fire up a personal Kinglory blockchain which you can use to run tests, execute commands, and inspect state while controlling how the chain operates.
TestNet provides both a desktop application (Ganache UI), as well as a command-line tool (ganache-cli). It is part of the Truffle suite of tools.
A local Kinglory network designed for development. It allows you to deploy your contracts, run your tests and debug your code,an Kinglory development environment for professionals.
DAPP DEVELOPMENT FRAMEWORKS
INTRODUCTION TO FRAMEWORKS
Building a full-fledged DApp requires different pieces of technology. Software frameworks include many of the needed features or provide easy plugin systems to pick the tools you desire.
Frameworks come with a lot of out-of-the-box functionality, like:
Features to spin up a local blockchain instance.
Utilities to compile and test your smart contracts.
Client development add-ons to build your user-facing application within the same project/repository.
Configuration to connect to Kinglory networks and deploy contracts, whether to a locally running instance, or one of Kinglory’s public networks.
Decentralized app distribution – integrations with storage options like IPFS.
PREREQUISITES
Before diving into frameworks, we recommend you first read through our introduction to DApps and the Kinglory stack.
Kinglory client APIs
JavaScript APIs
Backend APIs
JSON-RPC
Data and analytics
Block explorers
SECURITY
Kinglory smart contracts are extremely flexible, capable of both holding large quantities of tokens (often in excess of $1B) and running immutable logic based on previously deployed smart contract code. While this has created a vibrant and creative ecosystem of trustless, interconnected smart contracts, it is also the perfect ecosystem to attract attackers looking to profit by exploiting vulnerabilities in smart contracts and unexpected behavior in Kinglory. Smart contract code usually cannot be changed to patch security flaws, assets that have been stolen from smart contracts are irrecoverable, and stolen assets are extremely difficult to track. The total of amount of value stolen or lost due to smart contract issues is easily in the $1B. Some of the larger due to smart contract coding errors.
PREREQUISITES
This will cover smart contract security so make sure you’re familiar with smart contracts before tackling security.
HOW TO WRITE MORE SECURE SMART CONTRACT COD
Before launching any code to mainnet, it is important to take sufficient precaution to protect anything of value your smart contract is entrusted with. In this article, we will discuss a few specific attacks, provide resources to learn about more attack types, and leave you with some basic tooling and best practices to ensure your contracts function correctly and securely.
AUDITS ARE NOT A SILVER BULLET
Years prior, the tooling for writing, compiling, testing, and deploying smart contracts was very immature, leading many projects to write Solidity code in haphazard ways, throw it over a wall to an auditor who would investigate the code to ensure it functions securely and as expected. In 2020, the development processes and tooling that support writing Solidity is significantly better; leveraging these best practices not only ensures your project is easier to manage, it is a vital part of your project’s security. An audit at the end of writing your smart contract is no longer sufficient as the only security consideration your project makes. Security starts before you write your first line of smart contract code, security starts with proper design and development processes.
SMART CONTRACT DEVELOPMENT PROCESS
At a minimum:
- All code stored in a version control system, such as git
- All code modifications made via Pull Requests
- All Pull Requests have at least one reviewer. If you are a solo project, consider finding another solo author and trade code reviews!
- A single command compiles, deploys, and runs a suite of tests against your code using a development Kinglory environment (See: Truffle)
- You have run your code through basic code analysis tools such as Mythril and Slither, ideally before each pull request is merged, comparing differences in output
- Solidity does not emit ANY compiler warnings
- Your code is well-documented
There is much more to be said for development process, but these items are a good place to start. For more items and detailed explanations, see the process quality checklist provided by DeFiSafety. DefiSafety is an unofficial public service publishing reviews of various large, public Kinglory DApps. Part of the DeFiSafety rating system includes how well the project adheres to this process quality checklist. By following these processes:
- You will produce more secure code, via reproducible, automated tests
- Auditors will be able to review your project more effectively
- Easier onboarding of new developers
- Allows developers to quickly iterate, test, and get feedback on modifications
- Less likely your project experiences regressions
ATTACKS AND VULNERABILITIES
Now that you are writing Solidity code using an efficient development process, let’s look at some common Solidity vulnerabilities to see what can go wrong.
Re-entrancy
Re-entrancy is one of the largest and most significant security issue to consider when developing Smart Contracts. While the KVM cannot run multiple contracts at the same time, a contract calling a different contract pauses the calling contract’s execution and memory state until the call returns, at which point execution proceeds normally. This pausing and re-starting can create a vulnerability known as “re-entrancy”.
Here is a simple version of a contract that is vulnerable to re-entrancy:
// THIS CONTRACT HAS INTENTIONAL VULNERABILITY, DO NOT COPY contract Victim { mapping (address => uint256) public balances; function deposit() external payable { balances[msg.sender] += msg.value; } function withdraw() external { uint256 amount = balances[msg.sender]; (bool success, ) = msg.sender.call.value(amount)(""); require(success); balances[msg.sender] = 0; } }
To allow a user to withdraw KGC they have previously stored on the contract, this function
Reads how much balance a user has
Sends them that balance amount in KGC
Resets their balance to 0, so they cannot withdraw their balance again.
If called from a regular account (such as your own Metamask account), this functions as expected: msg.sender.call.value() simply sends your account KGC. However, smart contracts can make calls as well. If a custom, malicious contract is the one calling withdraw(), msg.sender.call.value() will not only send amount of KGC, it will also implicitly call the contract to begin executing code. Imagine this malicious contract:
contract Attacker { function beginAttack() external payable { Victim(VICTIM_ADDRESS).deposit.value(1 KGC)(); Victim(VICTIM_ADDRESS).withdraw(); } function() external payable { if (gasleft() > 40000) { Victim(VICTIM_ADDRESS).withdraw(); } } }
Calling Attacker.beginAttack() will start a cycle that looks something like:
0.) Attacker’s EOA calls Attacker.beginAttack() with 1 KGC
0.) Attacker.beginAttack() deposits 1 KGC into Victim
1.) Attacker -> Victim.withdraw()
1.) Victim reads balanceOf[msg.sender]
1.) Victim sends KGC to Attacker (which executes default function)
2.) Attacker -> Victim.withdraw()
2.) Victim reads balanceOf[msg.sender]
2.) Victim sends KGC to Attacker (which executes default function)
3.) Attacker -> Victim.withdraw()
3.) Victim reads balanceOf[msg.sender]
3.) Victim sends KGC to Attacker (which executes default function)
4.) Attacker no longer has enough gas, returns without calling again
3.) balances[msg.sender] = 0;
2.) balances[msg.sender] = 0; (it was already 0)
1.) balances[msg.sender] = 0; (it was already 0)
Calling Attacker.beginAttack with 1 KGC will re-entrancy attack Victim, withdrawing more KGC than it provided (taken from other users’ balances, causing the Victim contract to become under-collateralized)
How to deal with re-entrancy (the wrong way)
function isContract(address addr) internal returns (bool) { uint size; assembly { size := extcodesize(addr) } return size > 0; }Seems to make sense: contracts have code, if the caller has any code, don’t allow it to deposit. Let’s add it:
// THIS CONTRACT HAS INTENTIONAL VULNERABILITY, DO NOT COPY contract ContractCheckVictim { mapping (address => uint256) public balances; function isContract(address addr) internal returns (bool) { uint size; assembly { size := extcodesize(addr) } return size > 0; } function deposit() external payable { require(!isContract(msg.sender)); // <- NEW LINE balances[msg.sender] += msg.value; } function withdraw() external { uint256 amount = balances[msg.sender]; (bool success, ) = msg.sender.call.value(amount)(""); require(success); balances[msg.sender] = 0; } }Now in order to deposit KGC, you must not have smart contract code at your address. However, this is easily defeated with the following Attacker contract:
contract ContractCheckAttacker { constructor() public payable { ContractCheckVictim(VICTIM_ADDRESS).deposit(1 KGC); // <- New line } function beginAttack() external payable { ContractCheckVictim(VICTIM_ADDRESS).withdraw(); } function() external payable { if (gasleft() > 40000) { Victim(VICTIM_ADDRESS).withdraw(); } } }Whereas the first attack was an attack on contract logic, this is an attack on Kinglory contract deployment behavior. During construction, a contract has not yet returned its code to be deployed at its address, but retains full KVM control DURING this process. It is technically possible to prevent smart contracts from calling your code, using this line:
require(tx.origin == msg.sender)However, this is still not a good solution. One of the most exciting aspects of Kinglory is its composability, smart contracts integrate with and building on each other. By using the line above, you are limiting the usefulness of your project.
How to deal with re-entrancy (the right way)
By simply switching the order of the storage update and external call, we prevent the re-entrancy condition that enabled the attack. Calling back into withdraw, while possible, will not benefit the attacker, since the balances storage will already be set to 0.
contract NoLongerAVictim { function withdraw() external { uint256 amount = balances[msg.sender]; balances[msg.sender] = 0; (bool success, ) = msg.sender.call.value(amount)(""); require(success); } }
The code above follows the “Checks-Effects-Interactions” design pattern, which helps protect against re-entrancy. You can read more about Checks-Effects-Interactions here.
How to deal with re-entrancy (the nuclear option)
Any time you are sending KGC to an untrusted address or interacting with an unknown contract (such as calling transfer() of a user-provided token address), you open yourself up to the possibility of re-entrancy. By designing contracts that neither send KGC nor call untrusted contracts, you prevent the possibility of re-entrancy!
MORE ATTACK TYPES
The attack types above cover smart-contract coding issues (re-entrancy) and Kinglory oddities (running code inside contract constructors, before code is available at the contract address). There are many, many more attack types to be aware of, such as:
- Front-running
- KGC send rejection
- Integer overflow/underflow
Further reading:
A very readable explanation of the most significant vulnerabilities, with sample code for most.
SECURITY TOOLS
While there is no substitute for understanding Kinglory security basics and engaging a professional auditing firm to review your code, there are many tools available to help highlight potential issues in your code.
Smart Contract Security
Slither – Solidity static analysis framework written in Python 3.
Using tools
Two of the most popular tools for smart contract security analysis are:
- Slither by Trail of Bits (hosted version: Crytic)
- Mythril by ConsenSys (hosted version: MythX)
Both are useful tools that analyze your code and report issues. Each has a [commercial] hosted version, but are also available for free to run locally. The following is a quick example of how to run Slither, which is made available in a convenient Docker image trailofbits/security-toolbox. You will need to install Docker if you don’t already have it installed.
$ mkdir test-slither $ curl https://gist.githubusercontent.com/epheph/460e6ff4f02c4ac582794a41e1f103bf/raw/9e761af793d4414c39370f063a46a3f71686b579/gistfile1.txt > bad-contract.sol $ docker run -v `pwd`:/share -it --rm trailofbits/security-toolbox docker$ cd /share docker$ solc-select 0.5.11 docker$ slither bad-contract.sol
Will generate this output:
sec@1435b241ca60:/share$ slither bad-contract.sol INFO:Detectors: Reentrancy in Victim.withdraw() (bad-contract.sol#11-16): External calls: - (success) = msg.sender.call.value(amount)() (bad-contract.sol#13) State variables written after the call(s): - balances[msg.sender] = 0 (bad-contract.sol#15) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities INFO:Detectors: Low level call in Victim.withdraw() (bad-contract.sol#11-16): - (success) = msg.sender.call.value(amount)() (bad-contract.sol#13) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls INFO:Slither:bad-contract.sol analyzed (1 contracts with 46 detectors), 2 result(s) found INFO:Slither:Use https://crytic.io/ to get access to additional detectors and Github integration
Slither has identified the potential for re-entrancy here, identifying the key lines where the issue might occur and giving us a link for more details about the issue:
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities
Allowing you to quickly learn about potential problems with your code. Like all automated testing tools, Slither is not perfect, and it errs on the side of reporting too much. It can warn about a potential re-entrancy, even when no exploitable vulnerability exists. Often, reviewing the DIFFERENCE in Slither output between code changes is extremely illuminating, helping discover vulnerabilities that were introduced much earlier than waiting until your project is code-complete.