Kinglory

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

Perhaps the best metaphor for a smart contract is a vending machine, as described by Nick Szabo. With the right inputs, a certain output is guaranteed. To get a snack from a vending machine: money + snack selection = snack dispensed This logic is programmed into the vending machine. A smart contract, like a vending machine, has logic programmed into it. Here’s a simple example of how this vending machine might look like as a smart contract:
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

Influenced by C++ and JavaScript. Statically typed (the type of a variable is known at compile time). Supports: Inheritance (you can extend other contracts). Libraries (you can create reusable code that you can call from different contracts – like static functions in a static class in other object oriented programming languages). Complex user-defined types.

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

Pythonic programming language Strong typing Small and understandable compiler code Deliberately has less features than Solidity with the aim of making contracts more secure and easier to audit. Vyper does not support:
  • Modifiers
  • Inheritance
  • Inline assembly
  • Function overloading
  • Operator overloading
  • Recursive calling
  • Infinite-length loops
  • Binary fixed points
For more information, read the Vyper rationale.

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+

If you’re new to Kinglory and haven’t done any coding with smart contract languages yet, we recommend getting started with Solidity or Vyper. Only look into Yul or Yul+ once you’re familiar with smart contract security best practices and the specifics of working with the KVM.

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

Prop
block.timestamp uint256
msg.sender
State variable

msg.sender

Description
Current block epoch timestamp
Sender of the message (current call)

FUNCTIONS

In the most simplistic terms, functions can get information or set information in response to incoming transactions. There are two types of function calls:
  • 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).
They can also be public or private
  • 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
Here’s a function for updating a state variable on a contract:
// 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:

  1. Writing to state variables.
  2. Emitting events.
  3. Creating other contracts.
  4. Using selfdestruct.
  5. Sending KGC via calls.
  6. Calling any function not marked view or pure.
  7. Using low-level calls.
  8. 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

When writing smart contracts, there is a good chance you’ll find yourself writing similar patterns over and over, like assigning an admin address to carry out protected operations in a contract, or adding an emergency pause button in the event of an unexpected issue. Smart contract libraries usually provide reusable implementations of these behaviors as libraries or via inheritance in Solidity. As an example, following is a simplified version of the Ownable contract from the OpenZeppelin Contracts library, which designs an address as the owner of a contract, and provides a modifier for restricting access to a method only to that owner.
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

For the KVM to be able to run your contract it needs to be in bytecode. Compilation turns this:
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

The compiler will also produce the Application Binary Interface (ABI) which you need in order for your application to understand the contract and call the contract’s functions. The ABI is a JSON file that describes the deployed contract and its smart contract functions. This helps bridge the gap between web2 and web3 A Javascript client library will read the ABI in order for you to call on your smart contract in your web app’s interface. Below is the ABI for the ERC-20 token contract. An ERC-20 is a token you can trade on Kinglory.
[
{
"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)

One might consider defeating re-entrancy by simply preventing any smart contracts from interacting with your code. You search stackoverflow, you find this snippet of code with tons of upvotes:
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. 

Integrated Development Environments (IDEs)

Programming languages

Kinglory
开发文档

Kinglory技术堆栈简介

像任何软件堆栈一样,完整的“Kinglory堆栈”将根据您的业务目标因项目而异。

但是,有一些Kinglory的核心技术可以为软件应用程序与kinglory区块链的交互提供心理模型。了解堆栈的各个层将帮助您了解将Kinglory集成到软件项目中的不同方式。

第1级:Kinglory虚拟机

Kinglory虚拟机(KVM)是Kinglory中智能合约的运行时环境。Kinglory区块链上的所有智能合约和状态更改均由交易执行。 KVM处理Kinglory网络上的所有交易处理。

与任何虚拟机一样,KVM在执行代码和执行机器(Kinglory节点)之间创建一个抽象级别。当前,KVM在遍布全球的数千个节点上运行。

在后台,KVM使用一组操作码指令来执行特定任务。这些(140个唯一的)操作码使KVM可以完成图灵完善,这意味着只要有足够的资源,KVM就可以计算几乎任何东西。

作为dapp开发人员,您不需要了解除KVM之外的其他知识,而且它可以可靠地为Kinglory上的所有应用程序供电,而无需停机。

第2级:智能合约

智能合约是在Kinglory区块链上运行的可执行程序。

智能合约使用特定的编程语言编写,这些语言可编译为KVM字节码(称为操作码的低级机器指令)。

智能合约不仅可以用作开源库,而且本质上是开放式API服务,可以24/7运行,不能被删除。智能合约提供了公共功能,应用程序(dapps)可以在不需要许可的情况下与之交互。任何应用程序都可以与已部署的智能合约集成以构成功能(例如数据馈送或分散式交换)。任何人都可以将新的智能合约部署到Kinglory,以添加自定义功能来满足其应用程序的需求。

作为dapp开发人员,仅当您想在Kinglory区块链上添加自定义功能时才需要编写智能合约。您可能会发现仅通过与现有的智能合约集成就可以满足项目的大部分或全部需求,例如,如果您想以稳定币支持支付或启用分散式代币交换。

第3级:Kinglory节点

为了使应用程序与Kinglory区块链进行交互(即读取区块链数据和/或将交易发送到网络),它必须连接到Kinglory节点。

Kinglory节点是运行软件的计算机-Kinglory客户端。客户端是一种kinglory的实现,它可以验证每个区块中的所有交易,从而确保网络安全和数据准确。Kinglory节点是Kinglory区块链。他们共同存储Kinglory区块链的状态,并就使区块链状态发生变化的交易达成共识。

通过将应用程序连接到Kinglory节点(通过JSON RPC规范),您的应用程序能够从区块链读取数据(例如用户帐户余额)以及向网络广播新交易(例如在用户帐户之间转移KGC)或执行智能合约的功能)。

级别4:Kinglory客户端API

许多便利库(由Kinglory的开源社区构建和维护)允许您的最终用户应用程序连接到Kinglory区块链并与之通信。

如果面向用户的应用程序是Web应用程序,则可以选择直接在前端npm安装JavaScript API。或者,您可能会选择使用Python或Java API在服务器端实现此功能。

尽管这些API并不是堆栈中必不可少的部分,但它们消除了直接与Kinglory节点进行交互的许多复杂性。它们还提供实用程序功能(例如将KGC转换为glory),因此作为开发人员,您可以花更少的时间处理Kinglory客户端的复杂性,而将更多的时间集中在应用程序的独特功能上。

级别5:最终用户应用程序

面向用户的应用程序是堆栈的顶层。这些是您今天经常使用和构建的标准应用程序:主要是Web和移动应用程序。

开发这些用户界面的方式基本上保持不变。通常,用户不需要知道他们正在使用的应用程序是使用区块链构建的。

准备选择您的技术堆栈了吗?

查看我们的指南,为您的Kinglory应用程序设置本地开发环境。

智能合约简介

什么是智能合约?

“智能合约”只是在Kinglory区块链上运行的程序。它是位于Kinglory区块链上特定地址的代码(其功能)和数据(其状态)的集合。

智能合约是一种Kinglory账户。这意味着他们有余额,可以通过网络发送交易。但是,它们不受用户控制,而是被部署到网络并按程序运行。然后,用户帐户可以通过提交执行在智能合约上定义的功能的交易来与智能合约进行交互。智能合约可以定义规则(如常规合约),并通过代码自动执行,为了兼容其他公链,Kinglory智能合约既可以使用java和C++编程,也可以使用其他公链的Solidity。

前提条件

在进入智能合约世界之前,请确保您已经阅读了账户,交易和Kinglory虚拟机。

数码贩卖机

智能合约的最佳隐喻也许是自动售货机。使用正确的输入,可以保证一定的输出。

要从自动售货机购买小吃:

money + snack selection = snack dispensed

此逻辑已编程到自动售货机中。

像自动售货机一样,智能合约中已编写了逻辑程序。这是一个简单的示例,说明这种自动售货机作为智能合约的外观:

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;
}
}

就像自动售货机消除了对供应商员工的需求一样,智能合约可以代替许多行业的中介机构。

免费

任何人都可以编写智能合约并将其部署到网络。您只需要学习如何使用智能合约语言进行编码,并拥有足够的KGC即可部署您的合约。从技术上讲,部署智能合约是一项交易,因此您需要以与为简单的KGC转移支付天然气相同的方式支付天然气。但是,用于合同部署的天然气成本要高得多。

Kinglory拥有开发人员友好的语言来编写智能合约:

  • Solidity
  • Vyper

【有关语言的更多信息】

但是,必须先对其进行编译,然后才能进行部署,以便Kinglory的虚拟机可以解释和存储合同。【有关编译的更多信息】

组成性

智能合约在Kinglory上是公开的,可以被认为是开放的API。这意味着您可以在自己的智能合约中调用其他智能合约,以极大地扩展可能的范围。合同甚至可以部署其他合同。

了解有关智能合约可组合性的更多信息。

局限性

仅智能合约无法获得有关“真实世界”事件的信息,因为它们无法发送HTTP请求。这是设计使然。依赖外部信息可能会破坏共识,这对于安全和权力下放很重要。

有多种方法可以使用预言机解决此问题。

智能合约语言

关于Kinglory的一个重要方面是,可以使用相对开发人员友好的语言对智能合约进行编程。如果您有使用Python或JavaScript的经验,则可以找到一种语法熟悉的语言。

两种最活跃和最维护的语言是:

  • Solidity
  • Vyper

更有经验的开发人员可能还希望使用Kinglory虚拟机的中间语言Yul或Yul的扩展Yul +。

前提条件

以前对编程语言(尤其是JavaScript或Python)的了解可以帮助您了解智能合约语言的不同之处。我们还建议您在深入了解语言比较之前,先将智能合约理解为一个概念。智能合约简介。

SOLIDITY

受C ++和JavaScript影响。

静态类型(在编译时已知变量的类型)。

支持:

继承(您可以扩展其他合同)。

库(您可以创建可从不同协定调用的可重用代码,例如其他面向对象的编程语言中的静态类中的静态函数)。

复杂的用户定义类型。

重要连结

文献资料
统一语言门户
坚强的榜样
GitHub
备忘单

合同示例

// 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);
}
}

该示例应使您了解Solidity合同语法是什么样的。有关功能和变量的详细说明,请参阅文档。

VYPER

Pythonic编程语言

强大的打字

小型易懂的编译器代码

故意具有比Solidity少的功能,目的是使合同更安全且更易于审核。 Vyper不支持:

  • 修饰符
  • 遗产
  • 内联组装
  • 函数重载
  • 运算符重载
  • 递归调用
  • 无限长循环
  • 二元固定点

有关更多信息,请阅读Vyper基本原理。

重要连结

  • 文献资料
  • Vyper的例子
  • GitHub
  • Vyper Gitter聊天室
  • 备忘单

例子

# 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)

该示例应使您了解Vyper合同的语法是什么样的。有关功能和变量的详细说明,请参阅文档。

YUL和YUL +

如果您不熟悉Kinglory,并且还没有使用智能合约语言进行任何编码,我们建议您开始使用Solidity或Vyper。仅当您熟悉智能合约安全最佳实践以及使用KVM的细节后,才研究Yul或Yul +。

YUL

Kinglory的中级语言。

支持KVM和Ewasm(一种Kinglory风味的WebAssembly),并且被设计为这两个平台的可用共同点。

可以使KVM和Ewasm平台同等受益的高级优化阶段的理想目标。

Yul +

对Yul的低级高效扩展。

最初设计用于乐观汇总合同。

Yul +可以看作是Yul的实验性升级建议,它为它添加了新功能。

重要连结

Yul文档

Yul +文档

Yul +游乐场

Yul +介绍帖子

合同示例

以下简单示例实现了幂函数。可以使用solc –strict-assembly –bin input.yul进行编译。该示例应存储在input.yul文件中。

{
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)
}

如果您已经对智能合约有丰富的经验,可以在此处找到Yul的完整ERC20实施。

如何选择

与任何其他编程语言一样,它主要是关于为正确的工作以及个人喜好选择正确的工具。

如果您还没有尝试过任何一种语言,请考虑以下几点:

关于Solidity有什么好处?

如果您是初学者,那么这里有许多教程和学习工具。有关详细信息,请参阅“学习编码”部分。

良好的开发人员工具可用。

Solidity具有庞大的开发人员社区,这意味着您很可能会很快找到问题的答案。

Vyper有什么优点?

对于想要编写智能合约的Python开发人员来说,这是上手的好方法。

Vyper具有较少的功能,这使其非常适合快速创建构思原型。

Vyper的目标是易于审核,并最大限度地提高人们的可读性。

Yul和Yul +有什么优点?

简单而实用的底层语言。

使您可以更接近原始KVM,这可以帮助优化合同的用气量。

语言比较

对于基本语法,合同生命周期,接口,运算符,数据结构,函数,控制流等的比较,请查看Auditless的备忘单。

智能合约的解剖

智能合约是在Kinglory上的某个地址运行的程序。它们由可以在接收到事务后执行的数据和函数组成。这是组成智能合约的概述。

先决条件

确保您首先阅读了智能合约。本文档假定您已经熟悉JavaScript或Python之类的编程语言。

数据

必须将任何合同数据分配到一个位置:存储或内存。在智能合约中修改存储成本很高,因此您需要考虑数据应该存放在哪里。

贮存

永久数据称为存储,并由状态变量表示。这些值将永久存储在区块链上。您需要声明类型,以便合同可以跟踪编译时需要在区块链上存储多少存储空间。

// Solidity example
contract SimpleStorage {
uint storedData; // State variable
// ...
}

# Vyper example
storedData: int128

如果您已经编程了面向对象的语言,那么您可能会熟悉大多数类型。但是,如果您不熟悉Kinglory开发,那么地址应该是新的。

地址类型可以保存Kinglory地址,该地址等于20字节或160位。它以十六进制表示法返回,且前导0x。

其他类型包括:

  • 布尔值
  • 整数
  • 定点数
  • 固定大小的字节数组
  • 动态大小的字节数组
  • 有理和整数文字
  • 字符串文字
  • 十六进制文字
  • 枚举

有关更多说明,请看一下文档:

【查看Vyper类型】

【请参阅实体类型】

记忆

仅在合约函数执行的整个生命周期内存储的值称为内存变量。由于这些不是永久存储在区块链上,因此使用起来便宜得多。

在Solidity文档中了解有关KVM如何存储数据(存储,内存和堆栈)的更多信息。

环境变量

Prop
block.timestamp uint256
msg.sender
State variable

msg.sender

Description
Current block epoch timestamp
Sender of the message (current call)

功能

用最简单的术语来说,函数可以获取信息或设置信息以响应传入的事务。

有两种类型的函数调用:

  • 内部–这些不会创建KVM调用
  • 内部函数和状态变量只能在内部访问(即从当前合同中或从其衍生的合同中)
  • 外部–这些确实会创建一个KVM调用
  • 外部功能是合同接口的一部分,这意味着可以从其他合同或通过交易调用它们。外部函数f不能在内部调用(即f()不起作用,但是this.f()有效)。

它们也可以是公共的或私人的

  • 可以在合同内部或通过消息从外部调用公共功能
  • 专用功能仅对定义的合同可见,而在派生合同中不可见
  • 函数和状态变量都可以设为公共或私有

这是一个用于更新合同上的状态变量的函数:

// Solidity example
function update_name(string value) public {
dapp_name = value;
}

类型为string的参数值传递给函数:update_name

它被宣布为公开,这意味着任何人都可以访问它

它不是声明视图,因此可以修改合同状态

查看功能

这些功能保证不会修改合同数据的状态。命令示例是“ getter”功能-例如,您可以使用它来接收用户的余额。

// Solidity example
function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerPizzaCount[_owner];
}

dappName: public(string)
@view
@public
def readName() -> string:
return dappName

什么被认为是修改状态:

  • 写入状态变量。
  • 发射事件。
  • 创建其他合同。
  • 使用自毁。
  • 通过通话发送以太币。
  • 调用任何未标记为view或pure的函数。
  • 使用低级调用。
  • 使用包含某些操作码的内联汇编。

构造函数

首次部署合同时,构造函数仅执行一次。像许多基于类的编程语言中的构造函数一样,这些函数通常将状态变量初始化为它们的指定值。

// 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

内建功能

除了您在合同上定义的变量和函数外,还有一些特殊的内置函数。最明显的例子是:

  • address.send()–Solidity
  • send(地址)– Vyper

这些允许合同将KGC发送到其他帐户。

编写功能

 

您的功能需要:

  • 参数变量和类型(如果它接受参数)
  • 内部/外部声明
  • 申报纯/视图/应付
  • 返回类型(如果返回值)
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;
}
}

完整的合同可能看起来像这样。在这里,构造函数为dapp_name变量提供了一个初始值。

活动和日志

通过事件,您可以从前端或其他订阅应用程序与智能合约进行通信。进行交易时,智能合约可以发出事件,并向前端可以处理的区块链写入日志。

注释示例

这些是用Solidity编写的一些示例。如果您想使用代码,则可以在Remix中与它们进行交互。

你好,世界

// 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;
}
}

代币

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);
    }
}

独特的数字资产

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;
    }
}

智能合约库

您无需从头开始编写项目中的每个智能合约。有许多开放源代码智能合约库可为您的项目提供可重用的构建块,从而使您不必重新发明轮子。

前提条件

在进入智能合约库之前,最好对智能合约的结构有一个很好的了解。如果尚未进行智能合约解剖,请直接进行。

库里有什么

通常,您可以在智能合约库中找到两种构建基块:可以添加到合约中的可重用行为以及各种标准的实现。

Unique digital asset

编写智能合约时,您很有可能会一遍又一遍地编写类似的模式,例如分配管理员地址以执行合约中受保护的操作,或者在出现意外问题时添加紧急暂停按钮。

智能合约库通常以库的形式或通过Solidity中的继承来提供这些行为的可重用实现。

例如,以下是OpenZeppelin Contracts库中Ownable合同的简化版本,该库将地址设计为合同的所有者,并提供了一个修饰符,用于限制仅对该所有者的方法访问。

contract Ownable {
address public owner;
constructor() internal {
owner = msg.sender;
}
modifier onlyOwner() {
require(owner == msg.sender, "Ownable: caller is not the owner");
_;
}
}

要在合同中使用这样的构建块,您需要先导入它,然后再在您自己的合同中对其进行扩展。这将使您可以使用基本Ownable合同提供的修饰符来保护自己的功能。

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);
}
}

另一个流行的示例是SafeMath或DsMath。这些是库(与基本合同相对),提供带有溢出检查的算术功能,而语言没有提供这些检查。最好使用这些库中的任何一个,而不是使用本机算术运算来保护您的合同以防溢出,否则可能会造成灾难性的后果!

标准品

为了促进可组合性和互操作性,Kinglory社区以ERC的形式定义了一些标准。您可以在标准部分中阅读有关它们的更多信息。

当将ERC包含在合同中时,最好是寻找标准的实现方式,而不是尝试自己开发。许多智能合约库都包含最流行的ERC的实现。例如,可以在HQ20,DappSys和OpenZeppelin中找到无处不在的ERC20可替代令牌标准。此外,一些ERC还提供规范的实现,作为ERC本身的一部分。

值得一提的是,某些ERC并不是独立的,而是对其他ERC的补充。例如,ERC2612为ERC20添加了扩展,以提高其可用性。

如何添加库

请始终参阅所包含库的文档,以获取有关如何将其包含在项目中的特定说明。使用npm打包了多个Solidity合同库,因此您只需npm安装它们即可。大多数用于编译合同的工具都会在智能合同库的node_modules中进行查找,因此您可以执行以下操作:

// 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 { }
}

无论使用哪种方法,在包含库时,请始终注意语言版本。例如,如果您以Solidity 0.5编写合同,则不能使用Solidity 0.6的库。

使用时

为您的项目使用智能合约库有很多好处。首先,它为您提供了可以包含在系统中的即用型构建块,而不需要自己编写代码,从而节省了时间。

安全性也是一大优点。开源智能合约库也经常受到严格审查。鉴于许多项目都依赖于它们,因此社区强烈激励人们不断对其进行审查。在应用程序代码中发现错误比在可重用合同库中发现错误更为常见。一些库还接受了外部审核,以提高安全性。

但是,使用智能合约库有将不熟悉的代码包含到项目中的风险。导入合同并将其直接包含到您的项目中很诱人,但是如果不了解该合同的功能,您可能会e由于意外行为在系统中无意中引入了问题。始终确保阅读要导入的代码的文档,然后再将代码本身纳入项目中,然后再对其进行检查!

最后,在决定是否包括库时,请考虑其整体用法。一个被广泛采用的社区的好处是拥有一个更大的社区,并有更多的眼睛关注该问题。使用智能合约进行构建时,安全性应该是您的主要重点!

测试智能合约

测试工具和库

Waffle-用于高级智能合约开发和测试的框架(基于KGCers.js)。

getwaffle.io的GitHub

Solidity-Coverage-替代性Solidity代码覆盖率工具。

GitHub

hKVM-KVM的实现,专门用于单元测试和调试智能合约。

GitHub
DappHub聊天

 

Whiteblock.io

文献资料
GitHub

OpenZeppelin测试环境-快速的智能合约测试。一站式设置,可提供出色的测试体验。

GitHub
文献资料

OpenZeppelin测试助手-用于kinglory智能合约测试的断言库。确保您的合同表现符合预期!

GitHub

编译智能合约

您需要编译合同,以便您的Web应用程序和Kinglory虚拟机(KVM)可以理解它。

前提条件

您可能会发现在阅读有关编译的内容之前阅读我们的智能合约和kinglory虚拟机简介很有帮助。

KVM

为了使KVM能够运行您的合同,它需要使用字节码。编译结果如下:

pragma solidity 0.4.24;
contract Greeter {
function greet() public constant returns (string) {
return "Hello";
}
}

进入这个

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

网络应用

编译器还将生成您需要的应用程序二进制接口(ABI),以使您的应用程序了解合同并调用合同的功能。

ABI是一个JSON文件,描述了已部署的合同及其智能合同功能。这有助于弥合web2和web3之间的鸿沟

Javascript客户端库将读取ABI,以便您在Web应用程序的界面中调用智能合约。

以下是ERC-20代币合约的ABI。 ERC-20是您可以在Kinglory上交易的代币。

[
{
"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"
}
]

部署智能合约

您需要部署智能合约,以使其对Kinglory网络的用户可用。

要部署智能合约,您只需发送Kinglory交易,其中包含已编译的智能合约的代码,而无需指定任何接收者。

前提条件

在部署智能合约之前,您应该了解Kinglory网络,交易和智能合约的结构。

部署合约也要花费KGC,因此您应该熟悉Kinglory的gas和费用。

最后,您需要在部署合同之前先对其进行编译,因此请确保已阅读有关编译智能合同的信息。

如何部署智能合约

这意味着您需要支付交易费,因此请确保您有一些KGC。

你需要什么

您合同的字节码–这是通过编译生成的。

天然气的Kinglory–您将像其他交易一样设置天然气限额,因此请注意,与简单的KGC转移相比,合同部署需要更多的天然气。

部署脚本或插件。

通过运行自己的,连接到公共节点或使用诸如Infura或Alchemy之类的服务通过API密钥访问Kinglory节点

部署后,您的合同将像其他帐户一样具有Kinglory地址。

相关工具

Remix-Remix IDE允许开发,部署和管理Kinglory(如Kinglory)的智能合约。

Remix

Tenderly -通过错误跟踪,警报,绩效指标和详细的合同分析功能轻松监视您的智能合同的平台。

tenderly.co
GitHub
Discord

智能合约组合

简介

智能合约在Kinglory上是公开的,可以被认为是开放的API。 您无需编写自己的智能合约即可成为dapp开发人员,只需要知道如何与之交互即可。 例如,您可以使用去中心化交易所Uniswap的现有智能合约来处理应用程序中的所有令牌交换逻辑-无需从头开始。 查看他们的一些合同。

开发网络

使用智能合约构建Kinglory应用程序时,您需要在本地网络上运行该应用程序,以查看其工作原理,然后再进行部署。

与您可以在计算机上运行本地服务器进行Web开发的方式类似,您可以使用开发网络创建本地区块链实例来测试dapp。这些Kinglory开发网络提供的功能可实现比公共测试网更快的迭代速度(例如,您无需处理从测试网水龙头获取KGC)。

前提条件

在进入开发网络之前,您应该了解Kinglory堆栈和Kinglory网络的基础知识。

什么是开发网络?

开发网络本质上是专门为本地开发设计的Kinglory客户端(Kinglory的实现)。

为什么不只在本地运行标准的Kinglory节点?

您可以运行一个节点(例如GKGC,OpenKGCereum或NKGCermind),但是由于开发网络是专门为开发而构建的,因此它们通常具有诸如以下功能:

  • 确定性地为您的本地区块链注入数据(例如具有KGC余额的账户)
  • 立即接收收到的每笔交易,顺序有序地挖掘区块
  • 增强的调试和日志记录功能

可用工具

注意:大多数开发框架都包含一个内置的开发网络。我们建议从一个框架开始,以设置您的本地开发环境。

安全帽网络

快速启动个人Kinglory区块链,可用于运行测试,执行命令和检查状态,同时控制链的运行方式。

同时提供了桌面应用程序和命令行工具。它是工具套件的一部分。

专为开发而设计的本地Kinglory网络。它允许您部署合同,运行测试和调试代码

DAPP开发框架–简化Kinglory开发的工具

框架简介

构建成熟的dapp需要不同的技术。软件框架包括许多必需的功能,或者提供简单的插件系统来选择所需的工具。

框架具有许多现成的功能,例如:

  • 启动本地区块链实例的功能。
  • 编译和测试您的智能合约的实用程序。
  • 客户端开发附加组件可在同一项目/存储库中构建面向用户的应用程序。
  • 配置以连接到kinglory网络并部署合同(无论是本地运行的实例还是Kinglory的公共网络之一)。
  • 分散的应用程序分发-与IPFS等存储选项集成。

前提条件

在进入框架之前,建议您首先通读dapp和Kinglory堆栈的介绍。

Kinglory client APIs

JavaScript APIs

Backend APIs

JSON-RPC

Data and analytics

Block explorers

安全

Kinglory智能合约非常灵活,既可以持有大量代币(通常超过$ 1B),又可以基于先前部署的智能合约代码运行不可变逻辑。虽然这创建了一个充满活力且富有创意的生态系统,该生态系统包含不信任的,相互连接的智能合约,但它也是吸引攻击者的理想生态系统,这些攻击者希望通过利用智能合约中的漏洞和Kinglory中的意外行为来获利。通常无法更改智能合约代码来修补安全漏洞,从智能合约中窃取的资产是无法恢复的,并且被盗资产极难追踪。由于智能合约问题而被盗或丢失的价值总额很容易就在$ 1B中。由于智能合约编码错误而导致的一些较大错误包括:

奇偶校验多重签名问题#1-损失3000万美元

奇偶校验多次签名问题#2-已锁定3亿美元

TheDAO骇客,360万个KGC!今日KGC价格超过$ 1B

前提条件

这将涵盖智能合约安全性,因此在处理安全性之前请确保您熟悉智能合约。

如何编写更安全的智能合约代码

在向主网启动任何代码之前,务必采取足够的预防措施来保护您的智能合约所委托的任何有价值的东西。在本文中,我们将讨论一些特定的攻击,提供资源以了解更多攻击类型,并为您提供一些基本工具和最佳实践,以确保您的合同正确而安全地运行。

审计不是银弹

几年前,用于编写,编译,测试和部署智能合约的工具还很不成熟,导致许多项目以偶然的方式编写Solidity代码,并将其丢给审计员,后者将调查代码以确保代码安全运行并如预期的那样。到2020年,支持撰写Solidity的开发流程和工具将显着改善;利用这些最佳实践不仅可以确保您的项目更易于管理,而且是项目安全的重要组成部分。编写智能合约结束时进行的审计已不足以作为您的项目所做的唯一安全考虑。安全性是在您编写智能合约代码的第一行之前开始的,安全性是从正确的设计和开发过程开始的。

智能合约开发过程

最低限度:

  • 所有代码存储在版本控制系统中,例如git
  • 通过拉取请求进行的所有代码修改
  • 所有拉取请求至少具有一名审阅者。如果您是一个单独的项目,请考虑寻找另一个单独的作者并进行贸易代码评论!
  • 单个命令使用Kinglory开发环境针对您的代码编译,部署和运行测试套件(请参阅:Truffle)
  • 您已经通过Mythril和Slither等基本代码分析工具运行了代码,理想情况下,在合并每个拉取请求之前,比较输出中的差异
  • Solidity不会发出任何编译器警告
  • 您的代码有据可查

在开发过程中还有很多话要说,但是这些项目是一个很好的起点。有关更多项目和详细说明,请参阅DeFiSafety提供的过程质量清单。 DefiSafety是一种非官方的公共服务,发布了各种大型公共KGCereum dApp的评论。 DeFiSafety评级系统的一部分包括项目遵守此过程质量检查表的程度。通过遵循以下过程:

  • 通过可重现的自动化测试,您将产生更安全的代码
  • 审核员将能够更有效地审查您的项目
  • 新开发者更容易入职
  • 允许开发人员快速迭代,测试并获得有关修改的反馈
  • 您的项目经历回归的可能性较小

攻击和脆弱性

现在,您正在使用高效的开发过程来编写Solidity代码,接下来让我们看一下一些常见的Solidity漏洞,看看有什么问题可以解决。

重入

重新开发是开发智能合约时要考虑的最大和最重要的安全问题之一。虽然EVM不能同时运行多个合同,但是调用另一个合同的合同会暂停调用合同的执行和内存状态,直到调用返回为止,此时执行可以正常进行。暂停和重新启动会创建一个称为“重新进入”的漏洞。

这是容易受到重新进入的合同的简单版本:

// 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;
}
}

为了允许用户提取他们先前存储在合约中的KGC,这个功能

读取用户的余额

向他们发送以KGC为单位的余额

将其余额重置为0,因此他们无法再次提取其余额。

如果从常规帐户(例如您自己的Metamask帐户)调用,则此功能将按预期运行:msg.sender.call.value()仅发送您的帐户KGC。但是,智能合约也可以拨打电话。如果一个自定义的恶意合同是调用withdraw()的合同,则msg.sender.call.value()不仅会发送一定数量的KGC,还会隐式调用该合同以开始执行代码。想象一下这个恶意合同:

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();
}
}
}

调用Attacker.beginAttack()将启动一个类似于以下内容的循环:

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)

用1个KGC呼叫Attacker.beginAttack将重新进入攻击受害者的行列,并提取比其提供的数量更多的KGC(从其他用户的余额中提取,导致受害者合同的抵押品不足)

如何处理重入(错误方式)

您可能会考虑通过简单地阻止任何智能合约与您的代码进行交互来克服重入。您搜索stackoverflow,会发现此代码片段带有大量支持:

function isContract(address addr) internal returns (bool) {
uint size;
assembly { size := extcodesize(addr) }
return size > 0;
}

似乎有道理:合同有代码,如果调用方有任何代码,则不允许其存入。让我们添加它:

// 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;
}
}

现在,要存入以太币,您的地址必须没有智能合约代码。但是,通过以下“攻击者”合同可以轻松击败它:

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();
}
}
}

第一次攻击是对合同逻辑的攻击,这是对Kinglory合同部署行为的攻击。在构建期间,合同尚未返回要在其地址处部署的代码,但在此过程中保留了完整的EVM控制。

从技术上讲,可以使用以下行来防止智能合约调用您的代码:

require(tx.origin == msg.sender)

但是,这仍然不是一个好的解决方案。Kinglory最令人兴奋的方面之一是它的可组合性,智能合约相互集成并相互建立。通过使用上面的行,您在限制项目的实用性。

如何应对重入(正确的方法)

通过简单地切换存储更新和外部调用的顺序,我们可以防止导致攻击的重新进入条件。可能的话,回调到提款将不会使攻击者受益,因为余额存储已经设置为0。

contract NoLongerAVictim {
function withdraw() external {
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call.value(amount)("");
require(success);
}
}

上面的代码遵循“检查-效果-交互”设计模式,他lps防止重新进入。您可以在此处阅读有关Checks-Effects-Interactions的更多信息

如何应对重入(核选择)

每当您将KGC发送到不受信任的地址或与未知合同进行交互(例如,调用用户提供的令牌地址的transfer())时,您就可以重新进入。通过设计既不发送KGC也不调用不信任合同的合同,可以防止重新进入的可能性!

更多攻击类型

上面的攻击类型包括智能合约编码问题(重入)和Kinglory奇数(合约构造函数内部的运行代码,然后在合约地址可用)。还有很多其他攻击类型需要注意,例如:

前跑

KGC发送拒绝

整数上溢/下溢

进一步阅读:

【Consensys智能合约已知攻击【-对最重要漏洞的可读性很强的解释,其中大多数示例代码。

【SWC注册表】-适用于Kinglory和智能合约的CWE的精选列表

安全工具

虽然除了了解Kinglory安全基础知识和聘请专业审核公司来审查您的代码外,还可以使用许多工具来突出显示代码中的潜在问题。

智能合约安全

Slither-用Python 3编写的Solidity静态分析框架。

使用工具

用于智能合约安全性分析的两种最受欢迎​​的工具是:

  • Slither by Bit of Bits(托管版本:Crytic)
  • ConsenSys的Mythril(托管版本:MythX)

两者都是有用的工具,可以分析您的代码并报告问题。每个都有一个[商业]托管版本,但也可以免费在本地运行。以下是有关如何运行Slither的快速示例,该示例可在便捷的Docker映像Trailofbits / KGC-security-toolbox中获得。如果尚未安装Docker,则需要安装它。

$ 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

将生成此输出:

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在这里确定了重新进入的可能性,确定了可能发生问题的关键线,并为我们提供了有关该问题的更多详细信息的链接:

参考:https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities

使您可以快速了解代码的潜在问题。像所有自动化测试工具一样,Slither也并非完美无缺,而且在报告过多方面也有失误。即使不存在可利用的漏洞,它也可以警告可能会再次进入。通常,检查代码更改之间Slither输出中的差异非常有启发性,有助于发现比等到项目完成之前更早引入的漏洞。

Integrated Development Environments (IDEs)

Programming languages