Subscribe to our free newsletter

To make sure you won't miss any valuable content we share with our community.

Creating NFTs with Smart Contracts and Python Brownie

In our previous article about the Non-Fungible tokens, we explained about the methods of ERC-721. In this tutorial, we are going to use those methods to write the simple_collectible.sol smart contract and deploy it on the Rinkeby chain. In other words, we are going to create NFTs with Python Brownie tools. But before that, we also want to connect our test wallet to https://testnets.opensea.io in order to create an account in OpenSea which is a marketplace for NFTs.

Creating NFTs with Python Brownie

In this tutorial, we are going to use those methods to write the simple_collectible.sol smart contract and deploy it on the Rinkeby chain. But before that, we also want to connect our test wallet to https://testnets.opensea.io in order to create an account in OpenSea which is a marketplace for NFTs.

nft

To begin writing our smart contract, we have 2 options:

1. Use Brownie not mix by typing the below command in the terminal: brownie bake nft-mix And we will see all the necessary files with scripts in them are created.

2. start Brownie from scratch: mkdir simpleNFT cd simpleNFT brownie init To better understand NFT smart contracts, we start from scratch. Now, let’s begin writing the smart contract of our NFT:

// SPDX-License-Identifier: MIT
pragma solidity 0.6.6;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract SimpleCollectible is ERC721 {
	uint256 public tokenCounter;
	constructor () public ERC721 ("Dogie", "DOG"){
		tokenCounter = 0;
}

	function createCollectible(string memory tokenURI) public returns (uint256){
		uint256 newTokenId = tokenCounter;
		_safeMint(msg.sender, newTokenId);
		_setTokenURI(newTokenId, tokenURI);
		tokenCounter = tokenCounter + 1;
		return newTokenId;
	}
}


Notice that we have used safeMint and setTokenURI from "@openzeppelin/contracts/token/ERC721/ERC721.sol" that we had imported at the beginning of our contract and we should also add it to the dependencies of the brownie-config.yaml file. Now, let’s deploy our contract using the below scripts at deploy_and_create.py file:

sample_token_uri = "ipfs://Qmd9MCGtdVz2miNumBHDbvj8bigSgTwnr4SbyH6DNnpWdt?filename=0-PUG.json"
OPENSEA_URL = "https://testnets.opensea.io/assets/{}/{}"
def main():
    account = get_account()
    simple_collectible = SimpleCollectible.deploy({"from":account})
    tx = simple_collectible.createCollectible(sample_token_uri,{"from": account})
    tx.wait(1)
    print(f"Awesome, you can{OPENSEA_URL.format(simple_collectible.address,
    	simple_collectible.tokenCounter() - 1)}")

Also, don’t forget the helpful_scripts.py file in the scripts folder:

from brownie import accounts, network, config, Contract
from web3 import Web3

LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["hardhat", "development", "ganache", "mainnet-fork"]

def get_account(index=None, id=None):
        if index:
		return accounts[index]
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		return accounts[0]
	if id:
		return accounts.load(id)
	return accounts.add(config["wallets"]["from_key"])


The complete brownie-config.yaml file:

dependencies:
	- OpenZeppelin/[email protected]
	- smartcontractkit/[email protected]

compiler:
	solc:
		remappings:
			- '@openzeppelin=OpenZeppelin/[email protected]'
			- '@chainlink=smartcontractkit/[email protected]'
dotenv: .env
wallets:
	from_key: ${PRIVATE_KEY}
networks:
	development:
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
	rinkeby:
		vrf_coordinator: '0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B'
		link_token: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709'
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000 # 0.1



And our .env file:

export PRIVATE_KEY=' Paste your private key here'
export WEB3_INFURA_PROJECT_ID='INFURA ID'
export ETHERSCAN_TOKEN=' Your API Key goes here'


Now that we have set everything up, it is time to compile our project: brownie compile Result: Brownie v1.18.1 - Python development framework for Ethereum Compiling contracts... Solc version: 0.6.6 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/[email protected]/ERC165 - OpenZeppelin/[email protected]/IERC165 - OpenZeppelin/[email protected]/SafeMath - OpenZeppelin/[email protected]/ERC721 - OpenZeppelin/[email protected]/IERC721 - OpenZeppelin/[email protected]/IERC721Enumerable - OpenZeppelin/[email protected]/IERC721Metadata - OpenZeppelin/[email protected]/IERC721Receiver - OpenZeppelin/[email protected]/Address - OpenZeppelin/[email protected]/Context - OpenZeppelin/[email protected]/EnumerableMap - OpenZeppelin/[email protected]/EnumerableSet - OpenZeppelin/[email protected]/Strings - SimpleCollectible Project has been compiled. Build artifacts saved at /home/mohamad/NFT-Simple/build/contracts And deploy our NFT smart contract: brownie run scripts/deploy_and_create --network rinkeby Result: Brownie v1.18.1 - Python development framework for Ethereum NftSimpleProject is the active project. Running 'scripts/deploy_and_create.py::main'... Transaction sent: 0x4e328bf153e05a1fc06fd4060ca447ede90e7f8337e737c16508674cd6c7a951 Gas price: 1.000000061 gwei Gas limit: 2017295 Nonce: 67 SimpleCollectible.constructor confirmed Block: 10499268 Gas used: 1833905 (90.91%) SimpleCollectible deployed at: 0x657191536F5C1ec2EcfA1bD9bD4e14Ca8047F7bc Transaction sent: 0xfba6f759c7619d09a1339c975c51073846d2409c473c4f65889870cd9eae48a2 Gas price: 1.000000061 gwei Gas limit: 279109 Nonce: 68 SimpleCollectible.createCollectible confirmed Block: 10499269 Gas used: 253736 (90.91%) SimpleCollectible.createCollectible confirmed Block: 10499269 Gas used: 253736 (90.91%) Awesome, you can view your NFT at https://testnets.opensea.io/assets/0x657191536F5C1ec2EcfA1bD9bD4e14Ca8047F7bc/0 As you can see, we have successfully created our NFT and we can see it using the link given in the terminal.

Wrapping Up

In this tutorial, we have managed to write the different parts of the simplest NFT project, from the smart contract to the deployments and dependencies, configurations, environment variables, and so on. In the end, we deployed the contract on the Rinkeby test network.

Download this Article in PDF format

metaverse

We Are Working on Metaverses

Metaverses, 3d modeling, 3d application, 3d websites, and even animations are just some of our vase expertise.

Arashtad Serivces
Tell us about your ideas
Fill in the Form
Blockchain Development

An Introduction to the Non-Fungible Tokens (NFTs)

In this tutorial, we are going to introduce the Non-Fungible Tokens or ERC-721 standard. In addition to that, we are going to get familiar with the interface of ERC-721 so that we can later use them in NFT deployments. Every NFT project also needs an ERC-721 JSON metadata that we are going to introduce in different sections.

Introduction to Non-Fungible Tokens

Non-Fungible or ERC-721 tokens are the kind of tokens that have unique features in other words, they cannot be exchanged by one another because they are all identified as unique. The use cases for NFTs are platforms that offer collectible items (like artworks), lottery tickets, numbered seats for cinemas, concerts, access keys, and so on.

ERC-721 Interface

As mentioned earlier, the NFTs follow the ERC-721 standard. Here we take a look at the ERC-721 interface, which we are going to use in the next parts of our NFT tutorials when we want to write a contract for our collectibles:

pragma solidity ^ 0.4.20;

interface ERC721 {
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
}


The above event called Transfer emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are created (`from` == 0) and destroyed (`to` == 0).
Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer. At the time of any transfer, the approved address for that NFT (if any) is reset to none.

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);


The above Approval event emits when the approved address for an NFT is changed or reaffirmed. The zero address indicates there is no approved address. When a Transfer event emits, this also indicates that the approved address for that NFT (if any) is reset to none.

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);


ApprovalForAll event emits when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner.

function balanceOf(address _owner) external view returns (uint256);


The above function counts all NFTs assigned to an owner. NFTs assigned to the zero address is considered invalid, and this function throws for queries about the zero address. This function returns the number of NFTs owned by `_owner`, possibly zero.
_owner = An address for whom to query the balance

function ownerOf(uint256 _tokenId) external view returns (address);


The above function finds the owner of an NFT. The NFTs that are assigned to zero address are considered invalid, and queries about them do throw. _tokenId is the identifier for an NFT. The function returns the address of the owner of the NFT.

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;


The above function transfers the ownership of an NFT from one address to another address. It throws an error in the following cases:
  1. `msg.sender` is not the current owner, an authorized operator, or the approved address for this NFT.
  2. `_from` is not the current owner.
  3. `_to` is the zero address.
  4. `_tokenId` is not a valid NFT.
When transfer is complete, this function checks if `_to` is a smart contract (code size > 0). If so, it calls `onERC721Received` on `_to` and throws an error if the return value is not:

`bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.


The attributes of the function are as follows:
  1. _from The current owner of the NFT
  2. _to The new owner
  3. _tokenId The NFT to transfer
  4. _data Additional data with no specified format, sent in the call to `_to`

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;


The above function transfers the ownership of an NFT from one address to another. This function works the same as the previous one with an extra data parameter, except that this function just sets data to “”.
The attributes of this function are as follows:
  1. _from The current owner of the NFT
  2. _to The new owner
  3. _tokenId The NFT to transfer

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;


The above function transfers ownership of an NFT. The caller is responsible to confirm that `_to` is capable of receiving NFTs otherwise they may be permanently lost. It throws an error if:
  1. `msg.sender` is not the current owner, an authorized operator, or the approved address for this NFT.
  2. `_from` is not the current owner.
  3. `_to` is the zero address.
  4. `_tokenId` is not a valid NFT.
The attributes of this function are:
  1. _from The current owner of the NFT
  2. _to The new owner
  3. _tokenId The NFT to transfer

function approve(address _approved, uint256 _tokenId) external payable;


The above function changes or reaffirms the approved address for an NFT. The zero address indicates there is no approved address. It throws an error if:
`msg.sender` is not the current NFT owner or an authorizedoperator of the current owner.
The attributes are:
  1. _approved: The new approved NFT controller
  2. _tokenId: The NFT to approve

function setApprovalForAll(address _operator, bool _approved) external;


The above function enables or disables the approval for a third party (“operator”) to manage all of `msg.sender`‘s assets. Emits the ApprovalForAll event. The contract MUST allow multiple operators per owner.
The attributes are:
  1. _operator: Address to add to the set of authorized operators
  2. _approved: True if the operator is approved, false to revoke approval

function getApproved(uint256 _tokenId) external view returns (address);


The above function gets the approved address for a single NFT. Throws error if `_tokenId` is not a valid NFT.
The function attributes are:
    1. _tokenId The NFT to find the approved address for
The function returns:
The approved address for this NFT or the zero address if there is none otherwise.

function isApprovedForAll(address _owner, address _operator) external view returns (bool);


The above function queries if an address is an authorized operator for another address. The parameters of this function are:
  1. _owner: The address that owns the NFTs
  2. _operator: The address that acts on behalf of the owner
The function returns:
True if `_operator` is an approved operator for `_owner`, false.

interface ERC165 {
            function supportsInterface(bytes4 interfaceID) external view returns (bool);
}


The above function is related to the ERC-165 interface and queries if a contract implements an interface. Interface identification is specified in ERC-165. This function uses less than 30,000 gas. The attributes of this function are:
    1. Param interfaceID The interface identifier, as specified in ERC-165.
The function returns:
`true` if the contract implements `interfaceID` and `interfaceID` is not 0xffffffff, `false` otherwise.

ERC-721 Metadata

We also have an ERC721 JSON metadata of each NFT in the following format which specifies the features of the NFT. And we are going to use it in our future NFT projects.

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to                                                 
                         which this NFT represents. Consider making any images at a width between 
                         320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}


Last Consideration

In this article, we have introduced the ERC-721 standard for NFTs (Non-fungible tokens). Furthermore, we have taken a deeper look at the ERC-721 interface functions and attributes in addition to its JSON metadata format.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Lottery Project Compiling: A Complete Tutorial

In this tutorial, We are going to finally compile the lottery project. After making sure that it has been successfully compiled, we will run all the written tests to make sure everything works correctly in the contract. In the end, we will deploy the Decentralized Application on the Rinkeby test network. To be able to interact with the deployed contract, we will get some test Link tokens from Rinkeby Faucet.

Compiling the Lottery Project Dapp

In this part, we are going to finally compile, run and test our Lottery Dapp. brownie compile The result should be: Brownie v1.18.1 - Python development framework for Ethereum 1.33MiB [00:03, 402kiB/s] New compatible solc version available: 0.7.0 Compiling contracts... Solc version: 0.7.0 Optimizer: Enabled Runs: 200 EVM Version: Istanbul WARNING: Unable to compile OpenZeppelin/[email protected] due to a CompilerError - you may still be able to import sources from the package, but will be unable to load the package directly. Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.4.26+commit.4563c3fc 100%|██████████████████████████████████████████████████████████████████████| 5.24M/5.24M [00:10<00:00, 498kiB/s] solc 0.4.26 successfully installed at: /home/mohamad/.solcx/solc-v0.4.26 Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.6.6+commit.6c089d02 100%|██████████████████████████████████████████████████████████████████████| 8.92M/8.92M [00:23<00:00, 378kiB/s] solc 0.6.6 successfully installed at: /home/mohamad/.solcx/solc-v0.6.6 Compiling contracts... Solc version: 0.6.12 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/openzeppelin-contr[email protected]/Ownable - OpenZeppelin/[email protected]/Context - smartcontractkit/[email protected]/LinkTokenReceiver - smartcontractkit/[email protected]/VRFConsumerBase - smartcontractkit/[email protected]/VRFRequestIDBase - smartcontractkit/[email protected]/AggregatorInterface - smartcontractkit/[email protected]/AggregatorV2V3Interface - smartcontractkit/[email protected]/AggregatorV3Interface - smartcontractkit/[email protected]/ChainlinkRequestInterface - smartcontractkit/[email protected]/LinkTokenInterface - smartcontractkit/[email protected]/SafeMathChainlink - Lottery - MockOracle - MockV3Aggregator Compiling contracts... Solc version: 0.4.26 Optimizer: Enabled Runs: 200 EVM Version: Byzantium Generating build data... - smartcontractkit/[email protected]/ERC677Token - smartcontractkit/[email protected]/ERC20 - smartcontractkit/[email protected]/ERC20Basic - smartcontractkit/[email protected]/ERC677 - smartcontractkit/[email protected]/ERC677Receiver - smartcontractkit/[email protected]/BasicToken - smartcontractkit/[email protected]/SafeMathChainlink - smartcontractkit/[email protected]/StandardToken - LinkToken Compiling contracts... Solc version: 0.6.6 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - smartcontractkit/[email protected]/VRFConsumerBase - smartcontractkit/[email protected]/VRFRequestIDBase - smartcontractkit/[email protected]/LinkTokenInterface - smartcontractkit/[email protected]/SafeMathChainlink - VRFCoordinatorMock Generating interface ABIs... Project has been compiled. Build artifacts saved at /home/mohamad/smartcontract_lottery/build/contracts

Applying the Tests

Now, we apply our tests with following command in the terminal: brownie test -k test_get_entrance_fee Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.42s ======================================== Terminating local RPC client...

Test Number 2

brownie test -k test_cant_enter_unless_started Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.10s ======================================== Terminating local RPC client...

Test Number 3

brownie test -k test_can_start_and_enter_lottery Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.10s ======================================== Terminating local RPC client...

Test Number 4

brownie test -k test_can_end_lottery Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.34s ======================================== Terminating local RPC client...

Test Number 5

brownie test -k test_can_pick_winner_correctly Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.53s ======================================== Terminating local RPC client…

Deploying Our Lottery Contract

All the tests have been successfully run. Now it is time to finally deploy our contract: brownie run scripts/deploy_lottery.py Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 SmartcontractLotteryProject is the active project. Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... Running 'scripts/deploy_lottery.py::main'... Transaction sent: 0x8dece070c593b0dcea9caa4740f89c80ff2fe29744f8e6b6ed26bdf879c7ec9f Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 MockV3Aggregator.constructor confirmed Block: 1 Gas used: 430659 (3.59%) MockV3Aggregator deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Transaction sent: 0xab119cd52792e53c56eee35f433c3f4b8ae409e8a5d397ac2dd16aa2d9d88e27 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1 LinkToken.constructor confirmed Block: 2 Gas used: 669136 (5.58%) LinkToken deployed at: 0x602C71e4DAC47a042Ee7f46E0aee17F94A3bA0B6 Transaction sent: 0xb549afbc42364cb3e72877bf6d3634d655fbe0b608a09e921a3f881983dff488 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 2 VRFCoordinatorMock.constructor confirmed Block: 3 Gas used: 276395 (2.30%) VRFCoordinatorMock deployed at: 0xE7eD6747FaC5360f88a2EFC03E00d25789F69291 Deployed! Transaction sent: 0xbb65e39ea0771823b51b07af3e19122d0a9be34b51e768a536a215a9571fdcc7 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 3 Lottery.constructor confirmed Block: 4 Gas used: 897156 (7.48%) Lottery deployed at: 0x6951b5Bd815043E3F842c1b026b0Fa888Cc2DD85 Deployed lottery! Transaction sent: 0xdada264c84b388a9674518ac1fff2a6e7c2851f9adf5dc7ecd871bed9d0c8629 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 4 Lottery.startLottery confirmed Block: 5 Gas used: 28902 (0.24%) Lottery.startLottery confirmed Block: 5 Gas used: 28902 (0.24%) The lottery is started! Transaction sent: 0x9556bd5fecc58be55b36e84b6612ae264d00fe685e4862f0458846a30e305e2e Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 5 Lottery.enter confirmed Block: 6 Gas used: 70995 (0.59%) Lottery.enter confirmed Block: 6 Gas used: 70995 (0.59%) You entered the lottery! Transaction sent: 0x94f77b768b68faefacdc57046a42caadf5d954a0d0e089bb551f6594cb44037e Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 6 LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) Fund contract! LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) Transaction sent: 0x6591ec2cc1bfcddb9aea2f80e2723a773506e71bc8b649d58cf02c9708da1847 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 7 Lottery.endLottery confirmed Block: 8 Gas used: 79626 (0.66%) Lottery.endLottery confirmed Block: 8 Gas used: 79626 (0.66%) 0x0000000000000000000000000000000000000000 is the new winner! Terminating local RPC client...

Getting Some Test Link

By the way, before the deployment of this contract, make sure you have some Link Rinkeby tokens in your Metamask wallet. If you don’t have any, you can first add a Link Rinkeby token from Rinkeby Etherscan (By entering the decimals and contract address and so on). Then use Rinkeby Faucet which gives you 10 Link tokens. By using this link, you can get a Rinkeby Link token to be able to test your project:

https://docs.chain.link/docs/link-token-contracts/#rinkeby

Deploying Lottery Project on the Rinkeby Testnet

After running all the tests successfully and deploying it on Ganache-CLI, it is finally time to deploy our Lottery smart contract on Rinkeby chain, and inter-act with it using Rinkeby etherscan. To do so, in the terminal, we type: brownie run scripts/deploy_lottery.py --network rinkeby Result: Brownie v1.18.1 - Python development framework for Ethereum SmartcontractLotteryProject is the active project. Running 'scripts/deploy_lottery.py::main'... Transaction sent: 0xd0172e3f2d1caf7a56b7a1f88b5b95d152843f38f8e2f0bba68d2a5c3b534df7 Gas price: 1.000006975 gwei Gas limit: 995904 Nonce: 54 Lottery.constructor confirmed Block: 10461557 Gas used: 905368 (90.91%) Lottery deployed at: 0x2a5D4140962F09f4a5B4F5f7C563af4Eb45B79b4 Waiting for https://api-rinkeby.etherscan.io/api to process contract... Verification submitted successfully. Waiting for result... Verification pending... Verification complete. Result: Pass - Verified Deployed lottery! Transaction sent: 0x7b803fc2bedd3a1be5fa689e527e317baa4177c35ba589c1ea69ce3d6834298c Gas price: 1.000008155 gwei Gas limit: 31572 Nonce: 55 Lottery.startLottery confirmed Block: 10461565 Gas used: 28702 (90.91%) Lottery.startLottery confirmed Block: 10461565 Gas used: 28702 (90.91%) The lottery is started! Transaction sent: 0x7f2f38f4cfc0d34753c2635e8477b4c32118c3211ea3a3e6613a034a83242f99 Gas price: 1.000008155 gwei Gas limit: 97460 Nonce: 56 Lottery.enter confirmed Block: 10461566 Gas used: 88600 (90.91%) Lottery.enter confirmed Block: 10461566 Gas used: 88600 (90.91%) You entered the lottery! Transaction sent: 0xe1c566be0f8aff444154cf2d655cfc75082d83a7d13b9fb04f4315db3cece877 Gas price: 1.000008524 gwei Gas limit: 56992 Nonce: 57 LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) Fund contract! LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) Transaction sent: 0x585048e0b048833c35f3bd8257425e1e6f1f781cbd507df97abd756e7343c606 Gas price: 1.000008079 gwei Gas limit: 174062 Nonce: 58 Lottery.endLottery confirmed Block: 10461568 Gas used: 153439 (88.15%) Lottery.endLottery confirmed Block: 10461568 Gas used: 153439 (88.15%) 0x25E681EE76469E4cF846567b772e94e082907117 is the new winner!

Rinkeby Etherscan

Now, we can copy the and paste 0x2a5D4140962F09f4a5B4F5f7C563af4Eb45B79b4 address in Rinkeby etherscan to be able to interact with your smart contract:

https://rinkeby.etherscan.io

Once you enter Etherscan and paste your address, you will be able to see your address, ABI of the contract, dependency contracts, contract creation code, and so on.

Interacting with the Deployed Contract

To interact with the deployed smart contract, head over to Read the contract and write the contract.

You can read some of the details of the public variables after deployment in the Read contract section:

You can also run another lottery using Write Contract section:

Connecting to Metamask Wallet

But, before writing anything in there, make sure you connect to your wallet by clicking on Connect to Web3 button:

Now you can start the lottery:

Enter the lottery:

And execute other functions:

And that’s it. You have written a lottery smart contract, deployed it on a test net, Rinkeby chain, and other networks, and learned how to interact with it using Rinkeby Etherscan!

Lottery Project: Done!

In this tutorial, we have managed to deploy the contract on Rinkeby Testnet and interact with it from the address of the deployment on the Rinkeby Etherscan. To make this interaction possible from Etherscan, we connected the Metamask account of the admin of the contract to this website and applied all the stages of the lottery from the contract section.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Lottery Project Using Brownie: A Full Scale Dapp

In this article, we are going to get started with the lottery project using Brownie. The main purpose of a lottery project in every network is to check the reliability of the randomness and use it for different purposes such as the lottery itself. In the lottery project, we are going to create a decentralized application using Brownie. In the end, we will be able to run the smart contract via Etherscan.

Using Brownie for Lottery Project

In this tutorial, we are going to first write a smart contract related to a lottery and write scripts related to testing and deploying the smart contract. We also want to make it a full-scale decentralized application, meaning that it is going to be an end-to-end Dapp with easy to use user experience. Every lottery needs a random variable. As randomness is a very complicated concept when it is going to be applied on the internet and here we are going to run it via the blockchain, it must be protected from hacks and cheating. What makes randomness really complex, is that we are dealing with deterministic variables rather than probabilistic ones. As a result, we are going to use some Chainlink tools to cover this complexity.

In this first part, we only focus on the lottery.sol related to the smart contracts, its dependency files, and the Brownie-config.yaml in addition to seeing how the Solidity scripts are written and how they work.

Getting Started with the Lottery Project

It is also important to notice that this tutorial is written to explain the GitHub repository published by Patrick Alpha C more explicitly and solve the probable issues that the programmers might face when running and facing bugs related to the Solidity version or the configuration of the project. With that said let’s get started:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";


We have first declared the version of Solidity and the license comment before that. Also, we have imported 3 necessary Solidity libraries including AggregatorV3Interface which is used for fetching the Ethereum price from Chainlink oracle, openzeppelin Ownable.sol which is used for determining the owner of the contract and finally the VRFConsumer.sol which is used to get a random number.

contract Lottery is VRFConsumerBase, Ownable {
        address payable[] public players;
	address payable public recentWinner;
	uint256 public randomness;
	uint256 public usdEntryFee;
	AggregatorV3Interface internal ethUsdPriceFeed;
	enum LOTTERY_STATE {
		OPEN,
		CLOSED,
		CALCULATING_WINNER
	}
	LOTTERY_STATE public lottery_state;
	uint256 public fee;
	bytes32 public keyhash;
	event RequestedRandomness(bytes32 requestId);

	constructor(address _priceFeedAddress,
			address _vrfCoordinator,
			address _link,
			uint256 _fee,
			bytes32 _keyhash
			) public VRFConsumerBase(_vrfCoordinator, _link) {
		usdEntryFee = 50 * (10**18);
		ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress);
		lottery_state = LOTTERY_STATE.CLOSED;
		fee = _fee;
		keyhash = _keyhash;
	}
}

We start the contract named Lottery and determine their declarations ownable, VRFConsumerBase. Inside the contract we define the variables that we are going to work with including:
  • The array of the addresses of the participants in the lottery defined here as players (declared as payable and public),
  • The recent winner who has won the prize in the last lottery,
  • The randomness which is the random number we receive from VRFC,
  • UsdEntryFee which is the minimum amount that a player needs to participate in the lottery, ethUsdPriceFeed which is the conversion rate of ETH to USD.
The lottery state is declared as Enum and has 3 states Open: Close, and Calculating winner.
  1. Open is when everyone can participate in the lottery,
  2. Closed is when nobody can participate
  3. Calculating is used when the random number related to the winner is being calculated.
We also have a constructor for some variables which are going to be explained later.

function enter() public payable {
	// $50 minimum
	require(lottery_state == LOTTERY_STATE.OPEN);
	require(msg.value >= getEntranceFee(), "Not enough ETH!");
	players.push(msg.sender);
}


The above function is related to the entrance to the lottery. At first, the state of the lottery is open and we check if the players pay the minimum entrance fee to participate in it.

function getEntranceFee() public view returns (uint256) {
	(, int256 price, , , ) = ethUsdPriceFeed.latestRoundData();
	uint256 adjustedPrice = uint256(price) * 10**10;
	// 18 decimals
	// $50, $2,000 / ETH
	// 50/2,000
	// 50 * 100000 / 2000
	uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;
	return costToEnter;
}


The above function is responsible for checking the price of the Ethereum in USD and determining the price of participation in ETH.

function startLottery() public onlyOwner {
	require(lottery_state == LOTTERY_STATE.CLOSED,"Can't start a new lottery yet!");
	lottery_state = LOTTERY_STATE.OPEN;
}


The above function starts the lottery and before that checks if the lottery has not yet got closed.

function endLottery() public onlyOwner {
	lottery_state = LOTTERY_STATE.CALCULATING_WINNER;
	bytes32 requestId = requestRandomness(keyhash, fee);	
	emit RequestedRandomness(requestId);
}


The above function gives the authority to only the owner of the contract. This function also determines the winner to end the lottery. This process is done by requesting a random number.

function fulfillRandomness(bytes32 _requestId, uint256 _randomness)internal override{
	require(lottery_state == LOTTERY_STATE.CALCULATING_WINNER,"You aren't there yet!");
	require(_randomness > 0, "random-not-found");
	uint256 indexOfWinner = _randomness % players.length;
	recentWinner = players[indexOfWinner];
	recentWinner.transfer(address(this).balance);
	// Reset
	players = new address payable[](0);
	lottery_state = LOTTERY_STATE.CLOSED;
	randomness = _randomness;
}


The above function requires the lottery state to be in the calculating winner. It also checks whether the given random number is positive. Then it uses the remainder operator to calculate the id of the winner from the random number it gets. After that, it transfers the balance of the contract to the player that has won the lottery And finally changes the state of the lottery to closed.

Configuration

Notice that up to here you need to enter the following into the Brownie-config.yaml:

dependencies:
	smartcontractkit/[email protected]
 	OpenZeppelin/[email protected]
compiler:
	solc:
		remappings:
			'@chainlink=smartcontractkit/[email protected]'
			'@openzeppelin=OpenZeppelin/[email protected]'
dotenv: .env
networks:
	default: development
	development:
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
	rinkeby:
		vrf_coordinator:'0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B'
		eth_usd_price_feed:'0x8A753747A1Fa494EC906cE90E9f37563A8AF630e'
		link_token: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709'
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
		verify: True
	mainnet-fork:
		eth_usd_price_feed:'0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
		verify: False
wallets:
	from_key: ${PRIVATE_KEY}



Up to here, we only need the following to be able to compile the Brownie project:

dependencies:
	smartcontractkit/[email protected]
 	OpenZeppelin/[email protected]
compiler:
	solc:
		remappings:
			'@chainlink=smartcontractkit/[email protected]'
			'@openzeppelin=OpenZeppelin/[email protected]'


In the next parts, we are going to see how we can deploy and test this smart contract on different networks.

Deployment of the Lottery Project Using Brownie

In this section, we are going to continue the lottery project and focus on its deployment of it. The main python file as always is the deploy.py where we are going to deploy the four stages of a lottery: deploy the lottery, start the lottery, enter the lottery and end the lottery. The prize here is paid with a Chainlink token. As a result, we will add the ChainlinkTokenInterface contract to the contracts directory.

Managing the Folders of the Lottery Project Directory

In order to deploy the smart contract that we have just written, we need to complete the deployment and test folders and also add some other files like the dependencies of our smart contract. As always, the first step is to tyoe in the terminal: brownie init Then, you will see the folders and files that are created afterward. Then it is time to write our deploy.py file. As you know every lottery contract has 4 stages, deploying the lottery, starting it, entering the lottery, and ending it. So accordingly, we have 4 functions:

from scripts.helpful_scripts import get_account, get_contract, fund_with_link
from brownie import Lottery, network, config
import time

def deploy_lottery():
	account = get_account()
	lottery = Lottery.deploy(get_contract("eth_usd_price_feed").address, 						 
		get_contract("vrf_coordinator").address,
		get_contract("link_token").address,
		config["networks"][network.show_active()]["fee"],
		config["networks"][network.show_active()]["keyhash"],
		{"from": account},
		publish_source=config["networks"][network.show_active()].get("verify", False),)								
	print("Deployed lottery!")
	return lottery


In every contract deployment, we should determine some of the specifications related to a contract such as contract addresses (address of the dependency contracts) and some config specifications about every network.

def start_lottery():
	account = get_account()
	lottery = Lottery[-1]
	starting_tx = lottery.startLottery({"from": account})
	starting_tx.wait(1)
	print("The lottery is started!")


The above function starts the lottery. At first, it gets the account address which uses the get account function from helpful_scripts.py, then it gets the latest lottery smart contract, After that, it starts the lottery transaction using the startLottery function inside the contract.

def enter_lottery():
	account = get_account()
	lottery = Lottery[-1]
	value = lottery.getEntranceFee() + 100000000
	tx = lottery.enter({"from": account, "value": value})
	tx.wait(1)
	print("You entered the lottery!")


The above function also gets the account and the latest lottery contract records, then determine the value of the entrance fee (notice that 10000000 that has been added up with the entrance fee, is not a big number as it is in the Wei unit). Then, we enter the participant by using the enter function from the lottery.sol contract.

def end_lottery():
	account = get_account()
	lottery = Lottery[-1]
	# fund the contract
	# then end the lottery
	tx = fund_with_link(lottery.address)
	tx.wait(1)
	ending_transaction = lottery.endLottery({"from": account})
	ending_transaction.wait(1)
	time.sleep(180)
	print(f"{lottery.recentWinner()} is the new winner!")


In the above function, again get the account and the lottery contract, fund the winner with some link token (using the function written in helpful_scripts.py), and ends the transaction using the function in the lottery contract.sol.

def main():
	deploy_lottery()
	start_lottery()
	enter_lottery()
	end_lottery()


In the above main function, we apply other functions in the sequence of a lottery.

Helpful_Scripts.py

Now, it is time to go after helpful_scripts.py file and explain the codes:

from brownie import (accounts, network, config, MockV3Aggregator, VRFCoordinatorMock, LinkToken, 
	Contract, interface,)
FORKED_LOCAL_ENVIRONMENTS = ["mainnet-fork", "mainnet-fork-dev"]
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]

def get_account(index=None, id=None):
	if index:
		return accounts[index]
	if id:
		return accounts.load(id)
	if ( network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS or network.show_active() in 
			FORKED_LOCAL_ENVIRONMENTS):
		return accounts[0]
	return accounts.add(config["wallets"]["from_key"])


The above function determines the address of the available account according to the active network.

contract_to_mock = {"eth_usd_price_feed": MockV3Aggregator,"vrf_coordinator": VRFCoordinatorMock,
	"link_token": LinkToken,}
def get_contract(contract_name):
	contract_type = contract_to_mock[contract_name]
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		if len(contract_type) <= 0:
			# MockV3Aggregator.length
			deploy_mocks()
			contract = contract_type[-1]
			# MockV3Aggregator[-1]
	else:
		contract_address = config["networks"][network.show_active()][contract_name]
		contract = Contract.from_abi(contract_type._name, contract_address, contract_type.abi)
	return contract


The above function will grab the contract addresses from the Brownie config when defined. Otherwise, it will deploy a mock version of that contract, and return that mock contract.

DECIMALS = 8
INITIAL_VALUE = 200000000000
def deploy_mocks(decimals=DECIMALS, initial_value=INITIAL_VALUE):
	account = get_account()
	MockV3Aggregator.deploy(decimals, initial_value, {"from": account})
	link_token = LinkToken.deploy({"from": account})
	VRFCoordinatorMock.deploy(link_token.address, {"from": account})
	print("Deployed!")


The above function deploys the mock version of the 2 contracts: 1. VRFCoordinator and 2. V3Aggregator contracts.

def fund_with_link(contract_address, account=None, link_token=None,amount=100000000000000000): # 0.1 LINK
	account = account if account else get_account()
	link_token = link_token if link_token else get_contract("link_token")
	tx = link_token.transfer(contract_address, amount, {"from": account})
	tx.wait(1)
	print("Fund contract!")
	return tx


The above function funds the winner with a link token. Notice that we should create a file in the interfaces folder for the link token contract and name it LinkTokenInterface.sol. You can copy and paste the contract below:

Link Token Interface Contract

The below contract which is called LinkTokenInterface.sol, should be added to the contacts folder. We use the functions of this contract in the helpful_scripts.py and deploy.py to fund the winner of the lottery with some ChainLink tokens. If you look at the contract in detail, you will see that the functions and methods are the same as of an ERC-20 token contract.

pragma solidity ^ 0.6.6;

interface LinkTokenInterface {
	function allowance(address owner, address spender) external view returns (uint256 remaining);
	function approve(address spender, uint256 value) external returns (bool success);
	function balanceOf(address owner) external view returns (uint256 balance);
	function decimals() external view returns (uint8 decimalPlaces);
	function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
	function increaseApproval(address spender, uint256 subtractedValue)external;
	function name() external view returns (string memory tokenName);
	function symbol() external view returns (string memory tokenSymbol);
	function totalSupply() external view returns (uint256 totalTokensIssued);
	function transfer(address to, uint256 value) external returns (bool success);
	function transferAndCall(
		address to,
		uint256 value,
		bytes calldata data
		) external returns (bool success);
	function transferFrom(
		address from,
		address to,
		uint256 value
		) external returns (bool success);
}


Compiling Our Complete Lottery Project Using Brownie for Final Deployment

In this section, following the lottery project, we are going to complete the scripts that should be written so that we can finally compile our complete contract and be able to deploy it fully. The focus of this section is on the testing of the contract, where we are going to test the different functionalities of different stages of the lottery project. In the following of the lottery project, we are going to complete the scripts that should be written so that we can finally compile our complete contract and be able to deploy it fully.

Brownie_config.sol

There are some .sol dependencies that need to be copied and pasted into the contracts/test folder so that we can use them to deploy our main lottery smart contract. So make sure you copy them from this link and paste them into your directory.

.env File

Also, in the .env file enter the private key of your test Metamask account (Do not use a Metamask wallet with real crypto in it), your Etherscan Token, and Infura ID.

export WEB3_INFURA_PROJECT_ID=''
export PRIVATE_KEY=''
export ETHERSCAN_TOKEN=''


Testing the Contract Functionalities

Now, every standard smart contract deployment needs testing and this one is not an exception, we start our test with test_lottery_unit.py:

from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS,get_account,fund_with_link,
	get_contract,)
from brownie import Lottery, accounts, config, network, exceptions
from scripts.deploy_lottery import deploy_lottery
from web3 import Web3
import pytest

def test_get_entrance_fee():
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	# Arrange
	lottery = deploy_lottery()
	# Act
	# 2,000 eth / usd
	# usdEntryFee is 50
	# 2000/1 == 50/x == 0.025
	expected_entrance_fee = Web3.toWei(0.025, "ether")
	entrance_fee = lottery.getEntranceFee()
	# Assert
	assert expected_entrance_fee == entrance_fee


The above test considers the price of ETH is 2000 dollars and the entrance fee is 50 dollars so the 0.025 ether is required to participate in the lottery. as a result, we expect the entrance fee to be 0.025 ether, so we call the getEn-tranceFee() function to check the validity of the result.

def test_cant_enter_unless_started():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	# Act / Assert
	with pytest.raises(exceptions.VirtualMachineError):
		lottery.enter({"from": get_account(), "value": lot-tery.getEntranceFee()})


The above test checks whether the participant can enter or not at the times the lottery has been closed or has not opened yet.

def test_can_start_and_enter_lottery():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	# Act
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	# Assert
	assert lottery.players(0) == account


The above test checks whether the player who has participated in the lottery is recorded in the list of lottery players in other words it checks if the player has entered or not.

def test_can_end_lottery():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	fund_with_link(lottery)
	lottery.endLottery({"from": account})
	assert lottery.lottery_state() == 2



The above test checks whether the end lottery function works. To do so, it first passes all the other tests and applies the other stages of the lottery and at the end tests the lottery state.

def test_can_pick_winner_correctly():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	lottery.enter({"from": get_account(index=1), "value": 	lot-tery.getEntranceFee()})
	lottery.enter({"from": get_account(index=2), "value": 	lot-tery.getEntranceFee()})
	fund_with_link(lottery)
	starting_balance_of_account = account.balance()
	balance_of_lottery = lottery.balance()
	transaction = lottery.endLottery({"from": account})
	request_id = transaction.events["RequestedRandomness"]["requestId"]
	STATIC_RNG = 777
	get_contract("vrf_coordinator").callBackWithRandomness(request_id, STATIC_RNG, lottery.address,
		{"from": account})
	# 777 % 3 = 0
	assert lottery.recentWinner() == account
	assert lottery.balance() == 0
	assert account.balance() == starting_balance_of_account + bal-ance_of_lottery
	


The above test uses all of the operations of the lottery that have been tested to test the correctness of the winner picking. To do so, it first enters an account into the lottery and at the end, tests whether the winner is in the same account, whether the lottery contract balance has turned 0 as a result of sending money to the winner, and whether the winner’s account has been added up with the balance of the lottery contract.

from brownie import network
import pytest
from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS,
	get_account, fund_with_link,)
from scripts.deploy_lottery import deploy_lottery
import time

def test_can_pick_winner():
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	fund_with_link(lottery)
	lottery.endLottery({"from": account})
	time.sleep(180)
	assert lottery.recentWinner() == account
	assert lottery.balance() == 0


The above test also does the same process in a little bit different manner.

It is important to know that we should do all of the tests one by one to be able to debug the functions. As you can see, we started our first test with the first stage of the contract.

Final Word on Lottery Project Deployment Using Brownie

Firstly, we have managed to get started with the lottery project and have written the smart contract for it. In addition to that, we have added some dependency contracts like VRFConsumerBase, Openzeppelin, and V3AggregatorInterface.

Secondly, we have managed to write the deploy.py and helpful_scripts.py to interact with the lottery smart contract and deploy the different stages of a lottery such as deploying the lottery, starting it, entering it using different accounts, and ending it. In the end, the winner is going to be awarded some Chainlink tokens.

Thirdly, we have managed to complete the whole lottery project script file so that we can finally compile and deploy the smart contract. In most of the parts of this tutorial, we focused on testing the contract scripts for testing the different functionalities related to the different stages of the contract.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we're working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

ERC-20 Event and Function + Sample Smart Contract

In this article, we are going to analyze the ERC-20 event and function deeper and get familiar with the building blocks of any ERC-20 smart contract. Learning these methods and events will help us write the smart contract of our desired token. Throughout this article, you will see the functionalities of all the smart contracts that we wrote for creating an ERC20 token in the last article.

Functions and Event of all ERC-20 contracts:

In general, every ERC-20 token has the below functions and events:

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)


ERC-20 Event


event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)


A Sample ERC-20 Smart Contract: Method & Event

For the following contract, we are going to explain more about the ERC-20 method and event (TokenERC20.sol):

pragma solidity ^0.6.0;

interface tokenRecipient {
	function receiveApproval(address _from, uint256 _value, address _token, bytes calldata 
                _extraData) external;
}

contract TokenERC20 {
	string public name;
	string public symbol;
	uint8 public decimals = 18;
	uint256 public totalSupply;
}

Name, symbol, decimals, and total supply are the main variables of every ERC-20 smart contract.

mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;


Created a mapping between the addresses and balances (array of addresses and balances).

event Transfer(address indexed from, address indexed to, uint256 value);

A public event that will inform the clients about the transfer (sender, receiver, and value sent)

event Approval(address indexed _owner, address indexed _spender, uint256 _value);

A public event that will inform the clients about the transfer (sender, receiver, and value sent).

event Burn(address indexed from, uint256 value);

The burn event will inform the clients about the amount that has been burnt.

constructor(uint256 initialSupply,
		string memory tokenName,
		string memory tokenSymbol
		) public {
	totalSupply = initialSupply * 10 ** uint256(decimals);
	balanceOf[msg.sender] = totalSupply;
	name = tokenName;
	symbol = tokenSymbol;
}


The above constructor determines the total supply according to the decimals. And transfers all of it to the balance of the owner of the contract. In end, the name and the symbol of the token are determined for display.

function _transfer(address _from, address _to, uint _value) internal {
	require(_to != address(0x0));
}

The receiver should not be addressed as 0x0 or the owner of the contract.

require(balanceOf[_from] >= _value);

The sender must have enough balance to send the desired value.

require(balanceOf[_to] + _value >= balanceOf[_to]);

After the transaction, the receiver’s balance must increase. In other words, the value must be greater or equal to zero.

uint previousBalances = balanceOf[_from] + balanceOf[_to];

Before the transfer, the balance of the sender and receiver is calculated for the later tests.

balanceOf[_from] -= _value;

After the transfer, the balance of the sender is subtracted by the value.

balanceOf[_to] += _value;

After the transfer, the balance of the receiver is added to the value of the transfer.

emit Transfer(_from, _to, _value);

The event is emitted to notify the clients about the transaction.

assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

We test whether, after the transfer, the sum of the balances equals the previous sum of balances.The following function uses the data of the _transfer to verify the success of the transaction.

function transfer(address _to, uint256 _value) public returns (bool success) {
	_transfer(msg.sender, _to, _value);
	return true;
}


The following function will allow the sender to send just the value to the receiver.

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
	require(_value <= allowance[_from][msg.sender]);
	allowance[_from][msg.sender] -= _value;
	_transfer(_from, _to, _value);
	return true;
}


The function below checks the output of the above function if it has returned true and will let the contract know.

function approveAndCall(address _spender, uint256 _value, bytes memory 					 
	_extraData) public returns (bool success) {
	tokenRecipient spender = tokenRecipient(_spender);
	if (approve(_spender, _value)) {
		spender.receiveApproval(msg.sender, _value, address(this), _extraData);							
		return true;
	}
}

The function below burns the amount of transfer value, or in other words, it will decrease the amount that has been transferred from the total supply.

function burn(uint256 _value) public returns (bool success) {
	require(balanceOf[msg.sender] >= _value);
	balanceOf[msg.sender] -= _value; // Subtract from the sender
	totalSupply -= _value;
	emit Burn(msg.sender, _value);
	return true;
}


The following function is the same as above with the difference that it will ask for the allowance of the sender for the burning.


function burnFrom(address _from, uint256 _value) public returns (bool success) {
	require(balanceOf[_from] >= _value);
	require(_value <= allowance[_from][msg.sender]);
	balanceOf[_from] -= _value
	allowance[_from][msg.sender] -= _value; 						
	totalSupply -= _value;
	emit Burn(_from, _value);
	return true;	
}


Final Word

In this article, we have studied an ERC-20 smart contract. We have analyzed the necessary functions, methods, and events that every ERC-20 token smart contract must have. Furthermore, we have taken a look at how a sample token contract has been written in solidity with the consideration of all the events, methods, and necessary functions. Notice, that you can write an ERC-20 smart contract in a very different way as long as you include the necessary method and event in it.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Tell us about your ideas
Fill in the Form
Blockchain Development

How to Interact with the ERC-20 Smart Contract

In this tutorial we are going to run the ERC-20 smart contract deployment on the Rinkeby chain and see the actual token created in our Metamask wallet. After you create and deploy your ERC-20 smart contract, you will be able to see that this token is added on the Rinkeby Etherscan, and by adding the address and specifications of the token, you can add it to your wallet and also you will be able to do the same on Uniswap afterward to be able to swap your token with other tokens.

ERC-20 Smart Contract

In our last related article on how to create an ERC-20 token, we learned how to create a token with the ERC-20 standard. Moreover, we showed how to run it via Ganache-CLI. In this tutorial, we are going to run the very same deployment on the Rinkeby network, and see the actual token created in our Metamask wallet. After you create and deploy your ERC-20 smart contract, you will be able to see that this token is added to the Rinkeby Etherscan. Then, by adding the address and specifications of the token, you can add it to your wallet. Also, you will be able to do the same on Uniswap afterward to be able to swap your token with other tokens (notice that swapping your token is only possible on Mainnet but not on the Rinkeby test network).

Deploying on Rinkeby Testnet:

To begin with the deployment of our ERC-20 token, type the following command in the terminal:

brownie run scripts/1_deploy_token.py --network rinkeby

Result:

Brownie v1.18.1 - Python development framework for Ethereum Compiling contracts... Solc version: 0.6.12 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - TokenERC20 - tokenRecipient Compiling contracts... Solc version: 0.8.13 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/[email protected]/ERC20 - OpenZeppelin/[email protected]/IERC20 - OpenZeppelin/[email protected]/IERC20Metadata - OpenZeppelin/[email protected]/Context - OurToken MytokenProject is the active project. Running 'scripts/1_deploy_token.py::main'... Transaction sent: 0xa85a8d1595d290657f726f1533d105c2d2b6604e051409dc0c2e4d9a74bf547e Gas price: 1.000000019 gwei Gas limit: 745852 Nonce: 62 TokenERC20.constructor confirmed Block: 10468341 Gas used: 678048 (90.91%) TokenERC20 deployed at: 0xDD7f08A04c3d66beD75aFFA5f0e70e46de4567eb

Finding the token on Rinkeby Etherscan:

By copying and pasting the “0xDD7f08A04c3d66beD75aFFA5f0e70e46de4567eb” into the Rinkeby Etherscan, you will be able to see that we have indeed created a token called Token:

Adding the token to the Metamask:

Now, let’s head over to our Metamask, and in the Rinkeby network, import our new token by entering the address of the contract deployment:

ERC-20 smart contract

Adding the token to the Metamask:

Now, let’s head over to our Metamask, and in the Rinkeby network, import our new token by entering the address of the contract deployment:

ERC-20 smart contract ERC-20 smart contract

And you will see that our initial supply which was 1000 has been multiplied by 10 to power our decimals (which was 18). As a result, you can see 21 zeros after one as the total supply (All of which are in our account).

Transferring the tokens:

Now, we can send these tokens to other accounts. For example, here we transfer our TKN token to account 2.

ERC-20 smart contract

Importing the token in Uniswap:

We can also go to Uniswap and see if we can add our token to it.

ERC

let’s first connect to our Metamask wallet:

ERC

Then, press the “Select A Token” button. And in order to add your token in Uniswap, you should first enter your address in the box and you should be able to find the TKN token. Press import:

ERC

And we have our token in Uniswap!

ERC

Last Thought on ERC-20 Smart Contract

In this article, we have managed to run the deployment of the ERC-20 smart contract on the Rinkeby chain test network. Then, we copied the address of the deployed contract in the Rinkeby Etherscan and found our created token there. Afterward, we entered the specifications of the token in the add token section of the Metamask wallet and added the created token to it. Finally, we imported our token into the Uniswap Rinkeby Testnet.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
ThreeJS Development

How to Create an ERC-20 Token + Writing Smart Contract

In this tutorial, we are going to get familiar with the methods and events of the ERC-20 token. In addition to that, we want to create an ERC-20 token on the Rinkeby Testnet. By writing the smart contract, the deployment python file, helpful_scripts.py, and other files that help the creation and deployment of the ERC-20 token smart contract. This tutorial will go on in the next articles where we will explain the ERC-20 smart contract deeper and provide guidelines to add the created tokens to your Metamask wallet.

What Is ERC-20?

The ERC-20 is a token standard that implements an API for tokens within smart contracts. The acronym stands for Ethereum Request for Comments 20. The ERC-20 proposes a standard for fungible tokens (as opposed to Non-Fungible Tokens or NFTs that use ERC- 721) meaning that they have a property that makes each of them the same. This standard provides the following functionalities:

    1. Transfering tokens from one account to another.
    2. Tetting the balance of an account.
    3. Getting the total supply of the token available on the network.
    4. Approving whether a third party can spend tokens from an account.

Methods and Events of an ERC-20 Token

If a smart contract implements the events and methods of ERC-20, it can be called an ERC-20 token contract.

Methods:


function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)


Events:


event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)


Getting Started with the ERC-20 Token in Brownie

Now, we begin our project by typing in the terminal:

brownie init

And in our contracts folder, we create a file named OurToken.sol and paste the below contract we have copied from this link. You can change the name of the token whatever you want:


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract OurToken is ERC20 {
	// wei
	constructor(uint256 initialSupply) ERC20("OurToken", "OT") {
	_mint(msg.sender, initialSupply);
	}
}


We also create a deploy_token.py file in the scripts folder and paste the below code in it:


from brownie import accounts, config, TokenERC20, EasyToken
from scripts.helpful_scripts import get_account

initial_supply = 1000000000000000000000 # 1000
token_name = "Token"
token_symbol = "TKN"

def main():
	account = get_account()
	erc20 = TokenERC20.deploy(initial_supply, token_name, token_symbol, {"from": account})


And helpful_scripts.py goes like this:


from brownie import network, accounts, config

LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["hardhat", "development", "mainnet-fork"]

def get_account():
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		return accounts[0]
	if network.show_active() in config["networks"]:
		account = accounts.add(config["wallets"]["from_key"])
		return account
	return None
	


ERC-20 Token Smart Contract

And finally this is the contract that contains the ERC-20 methods and events (TokenERC20.sol):


pragma solidity ^0.6.0;

interface tokenRecipient {
	function receiveApproval(address _from, uint256 _value, address _token, bytes 	calldata _extraData) external;
	}

contract TokenERC20 {
	// Public variables of the token
	string public name;
	string public symbol;
	uint8 public decimals = 18;
	// 18 decimals is the strongly suggested default, avoid changing it
	uint256 public totalSupply;
	// This creates an array with all balances
	mapping (address => uint256) public balanceOf;
	mapping (address => mapping (address => uint256)) public allowance;
	// This generates a public event on the blockchain that will notify clients
	event Transfer(address indexed from, address indexed to, uint256 value);
	// This generates a public event on the blockchain that will notify clients
	event Approval(address indexed _owner, address indexed _spender, uint256 	_value);
	event Burn(address indexed from, uint256 value);
	constructor(
			uint256 initialSupply,
			string memory tokenName,
			string memory tokenSymbol
			) public {
		totalSupply = initialSupply * 10 ** uint256(decimals);  							 
                //Update total supply with the decimal amount

		balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens
		name = tokenName; // Set the name for display purposes
		symbol = tokenSymbol; // Set the symbol for display purposes
	}

	function _transfer(address _from, address _to, uint _value) internal {
		// Prevent transfer to 0x0 address. Use burn() instead
		require(_to != address(0x0));
		// Check if the sender has enough
		require(balanceOf[_from] >= _value);
		// Check for overflows
		require(balanceOf[_to] + _value >= balanceOf[_to]);
		// Save this for an assertion in the future
		uint previousBalances = balanceOf[_from] + balanceOf[_to];
		// Subtract from the sender
		balanceOf[_from] -= _value;
		// Add the same to the recipient
		balanceOf[_to] += _value;
		emit Transfer(_from, _to, _value);
		// Asserts are used to use static analysis to find bugs in your code. They should never 
                //fail 							
		assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
	}


	function transfer(address _to, uint256 _value) public returns (bool success) {
		_transfer(msg.sender, _to, _value);
		return true;
	}

	function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
		require(_value <= allowance[_from][msg.sender]); // Check allowance
		allowance[_from][msg.sender] -= _value;
		_transfer(_from, _to, _value);
		return true;
	}

	function approve(address _spender, uint256 _value) public returns (bool success) {
		allowance[msg.sender][_spender] = _value;
		emit Approval(msg.sender, _spender, _value);
		return true;
	}

	function approveAndCall(address _spender, uint256 _value, bytes memory extraData)				 
                              public returns (bool success) {
		tokenRecipient spender = tokenRecipient(_spender);
		if (approve(_spender, _value)) {
                              spender.receiveApproval(msg.sender, _value, address(this), _extraData);
			return true;
		}
	}

	function burn(uint256 _value) public returns (bool success) {
		require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
		balanceOf[msg.sender] -= _value; // Subtract from the sender
		totalSupply -= _value; // Updates totalSupply
		emit Burn(msg.sender, _value);
		return true;
	}

	function burnFrom(address _from, uint256 _value) public returns (bool success) {
		require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
		require(_value <= allowance[_from][msg.sender]); // Check allowance
		balanceOf[_from] -= _value; // Subtract from the targeted balance
		allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
		totalSupply -= _value; // Update totalSupply
		emit Burn(_from, _value);
		return true;
	}
}



We are going to explain the above contract deeper in the next article. But for now, it is time to deploy the contract itself:

brownie run scripts/1_deploy_token.py

Result:

Brownie v1.18.1 - Python development framework for Ethereum Erc20BrownieProject is the active project. Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... Running 'scripts/1_deploy_token.py::main'... Transaction sent: 0xe677ec7a8e17a7e750bb65f908bf77c92b5bbd368d975f7c3b36131544c9cf02 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 TokenERC20.constructor confirmed Block: 1 Gas used: 670648 (5.59%) TokenERC20 deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Terminating local RPC client…

From the above results, we are going to use the address that the smart contract is deployed at the "TokenERC20" Which is 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 in this case, and track in the Etherscan.

Final Word

In this article, we have got familiar with the methods and events of an ERC-20 token. Besides, we have created a project containing the ERC-20 token smart contract, the deployment python files, and so on to create a typical ERC-20 token with a certain number of tokens created in our Metamask wallet. In the tutorials, we will see how we can add these tokens in the Uniswap and also find them on the Etherscan.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Using Brownie to Switch between Different Networks

In this article, we are going to switch between the different available networks we can connect to, using Brownie. Then, we’ll write a statement in the Deploy.py to connect to the network requested in the terminal by the user (the developer). This kind of script for network management makes it easy for us to connect to any network that we want at any time it is required for testing and other purposes.

How to Switch between Networks

As you know, we have different networks to work with. We have already used some of them for deploying our contracts. Sometimes we need to write our deploy.py file in a way that we can switch between different networks and accounts and the deploy.py must understand which ones are available.

Switch between Different Networks

Before we get started with this task, we can check the keywords when we want to connect to different accounts. To look up the keyword related to any network, we write:

brownie networks list

Result:

Brownie v1.18.1 - Python development framework for Ethereum The following networks are declared: Ethereum ├─Mainnet (Infura): mainnet ├─Ropsten (Infura): ropsten ├─Rinkeby (Infura): rinkeby ├─Goerli (Infura): goerli └─Kovan (Infura): kovan Ethereum Classic ├─Mainnet: etc └─Kotti: kotti Arbitrum └─Mainnet: arbitrum-main Avalanche ├─Mainnet: avax-main └─Testnet: avax-test Aurora ├─Mainnet: aurora-main └─Testnet: aurora-test Binance Smart Chain ├─Testnet: bsc-test └─Mainnet: bsc-main Fantom Opera ├─Testnet: ftm-test └─Mainnet: ftm-main Harmony └─Mainnet (Shard 0): harmony-main Moonbeam └─Mainnet: moonbeam-main Optimistic Ethereum ├─Mainnet: optimism-main └─Kovan: optimism-test Polygon ├─Mainnet (Infura): polygon-main └─Mumbai Testnet (Infura): polygon-test XDai ├─Mainnet: xdai-main └─Testnet: xdai-test Development ├─Ganache-CLI: development ├─Geth Dev: geth-dev ├─Hardhat: hardhat ├─Hardhat (Mainnet Fork): hardhat-fork ├─Ganache-CLI (Mainnet Fork): mainnet-fork ├─Ganache-CLI (BSC-Mainnet Fork): bsc-main-fork ├─Ganache-CLI (FTM-Mainnet Fork): ftm-main-fork ├─Ganache-CLI (Polygon-Mainnet Fork): polygon-main-fork ├─Ganache-CLI (XDai-Mainnet Fork): xdai-main-fork ├─Ganache-CLI (Avax-Mainnet Fork): avax-main-fork └─Ganache-CLI (Aurora-Mainnet Fork): aurora-main-fork

As you can remember, we have used Rinkeby from Infura a number of times and here the keyword for Rinkeby (Infura) is Rinkeby. Here, when we work with Brownie, in order to define the Infura RPC URL on the .env file, we need to write it in a different format:
export WEB3_INFURA_PROJECT_ID=80ca094b614b44b3b647ceb01a2b70d0
Notice that if you write any word other than WEB3_INFURA_PROJECT_ID, you will have a problem working with the Rinkeby or other networks of Infura, because Brownie has some built-in scripts that read from the ID with only this name.

Swtich Between Networks via Network Management in Deploy.py

The next thing we should do when we want to check for networks available for deployment is to add the following function to our deploy.py file:


def get_account():
	if network.show_active() == "development":
		return accounts[0]
	else:
		return accounts.add(config["wallets"]["from_key"])


And whenever we want to define our account, we write:
Account = get_account()
It is also necessary to import the network from Brownie:
from Brownie import network
The above code checks whether we want to use our Ganache CLI test accounts or use any other accounts that we have defined its a private key to our .env file and introduced to Brownie.
In order to connect to Rinkeby account in our Metamask wallet (that we entered its private key in the .env file), and the Rinkeby test network, in the terminal we should type:

brownie run scripts/deploy.py --network rinkeby

Result:

Brownie v1.18.1 - Python development framework for Ethereum BrownieSimpleStorageProject is the active project. Running 'scripts/deploy.py::main'... Transaction sent: 0xbfe6fb9f152eeda4fc880fc5a5cb6f74b0d73b440ada91e8bef71fb7fccf1ccd Gas price: 1.000000012 gwei Gas limit: 367598 Nonce: 48 SimpleStorage.constructor confirmed Block: 10426128 Gas used: 334180 (90.91%) SimpleStorage deployed at: 0x1570258Ee66a921A3f5fdEA48f5ba54bE657AA8b 0 Transaction sent: 0x0a5d8886732d99e6045eede0958894a033e48225d6818c312491b178195576ed Gas price: 1.000000012 gwei Gas limit: 47842 Nonce: 49 SimpleStorage.store confirmed Block: 10426129 Gas used: 43493 (90.91%) SimpleStorage.store confirmed Block: 10426129 Gas used: 43493 (90.91%) 38

And you can see the transaction related to contract deployment and storing a value inside the contract has been successfully completed on the Rinkeby network. Because we are working with Rinkeby, the transaction is trackable on this link. To be able to track it, simply copy and paste the address of the transaction into the search bar of the Etherscan.

Etherscan

Reading from the Contracts

One of the useful and necessary steps in writing an application related to a smart contract is being able to read from the transaction. The following steps will help you retrieve the different properties of the deployed smart contracts.
First, create a file in the scripts folder and name it read_value.py then write the following code in it.


from brownie import SimpleStorage, accounts, config

def read_contract():
	print(SimpleStorage)

def main():
	read_contract()


The above code, reads the contract deployments and their transactions. Let’s see the result by typing in the terminal:

brownie run scripts/read_value.py --network rinkeby

Result:

Brownie v1.18.1 - Python development framework for Ethereum BrownieSimpleStorageProject is the active project. Running 'scripts/read_value.py::main'...

The above result shows an array inside which we can retrieve the first member by changing the code to:


from brownie import SimpleStorage, accounts, config

def read_contract():
	print(SimpleStorage[0])

def main():
	read_contract()


brownie run scripts/read_value.py --network rinkeby

Result:

Brownie v1.18.1 - Python development framework for Ethereum BrownieSimpleStorageProject is the active project. Running 'scripts/read_value.py::main'... 0x1570258Ee66a921A3f5fdEA48f5ba54bE657AA8b

If you take a closer look, you will see that the address given here, is just the same address that we saw on Etherscan (the address of the contract deployment). It is worth knowing that if you want to retrieve the latest address contract that has been deployed, instead of print(SimpleStorage[0]) you can write print (SimpleStorage[-1]).
Notice that Brownie already knows the address and the ABI of our smart contract because it has saved it in a .json file. Also, if we want to retrieve a number again without asking the blockchain for it, we can retrieve it in the read_value.py file by writing:


print(SimpleStorage[-1].retrieve()

Result:

38

Summing Up

In this article, we have managed to write a script that makes it easy to switch between different networks such as Testnet (like Rinkeby), Mainnet, and local (like Ganache CLI) for the users and the developers. With this script written in the deploy.py, we can choose the network at the time of running Brownie by simply calling the network’s name in the terminal.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Testing Deploy.py Script Using Brownie: A Full Analysis

In this post, we will complete testing our deploy.py script using Brownie in more detail so that we can see if there is any problem at all. And if there is, we can analyze it in more detail. This kind of detailed test and code analysis becomes handy, especially when the code is too long and complex that we cannot so easily understand where the unexpected result originates from.

Detailed Testing Deploy.py Using Brownie

For more detailed testing, we can write in the terminal:

brownie test -k

Which in our case will be:

brownie test -k test_updating_storage

And the result will be:

Brownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 2 items / 1 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py . [100%] =============================================== 1 passed, 1 deselected in 1.85s =============================================== Terminating local RPC client...

And if we write anything wrong in our code, by running:

brownie test --pdb

In the terminal, we will see:

Brownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 2 items Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py F >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> def test_deploy(): account = accounts[0] simple_storage = SimpleStorage.deploy({"from": account}) starting_value = simple_storage.retrieve() expected = 38 > assert starting_value == expected E assert 0 == 38 tests/test_simple_storage.py:10: AssertionError >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> > /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage/tests/test_simple_storage.py(10)test_deploy() -> assert starting_value == expected

This shows that in the first function we mistakenly expected the stored value to be 38 which was 0 at first. We can also do some sort of debugging next to PDB in the terminal for example we can write:

(Pdb) expected >> 38

Which shows 38 as the number stored in expected, or:

(Pdb) simple_storage.retrieve() >>0

By understanding the difference between the expected number and the retrieved one, we will be able to debug the code.

(Pdb) simple_storage

The better way to fully understand the problems and be able to find it, is to type in the terminal:

brownie test -s Brownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/mohamad/.local/pipx/venvs/eth-brownie/bin/python cachedir: .pytest_cache hypothesis profile 'brownie-verbose' -> verbosity=2, deadline=None, max_examples=50, stateful_step_count=10, report_multiple_bugs=False, database=DirectoryBasedExampleDatabase(PosixPath('/home/mohamad/.brownie/hypothesis')) rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 2 items Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py::test_deploy FAILED tests/test_simple_storage.py::test_updating_storage PASSED ========================================================== FAILURES =========================================================== _________________________________________________________ test_deploy _________________________________________________________ def test_deploy(): account = accounts[0] simple_storage = SimpleStorage.deploy({"from": account}) starting_value = simple_storage.retrieve() expected = 38 > assert starting_value == expected E assert 0 == 38 E +0 E -38 tests/test_simple_storage.py:10: AssertionError =================================================== short test summary info =================================================== FAILED tests/test_simple_storage.py::test_deploy - assert 0 == 38 ================================================= 1 failed, 1 passed in 1.94s ================================================= Terminating local RPC client…

As you can see in the result, the code and its variables are fully analyzed.

Fixing the Code

Now if we fix our code and change the expected in the first function to 0:


from brownie import SimpleStorage, accounts
def test_deploy():

	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	starting_value = simple_storage.retrieve()
	expected = 0
	assert starting_value == expected

def test_updating_storage():

	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	expected = 38
	simple_storage.store(expected,{"from":account})
	assert expected == simple_storage.retrieve()
	



And test our deployment again:

brownie test -s

We will see:

rownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/mohamad/.local/pipx/venvs/eth-brownie/bin/python cachedir: .pytest_cache hypothesis profile 'brownie-verbose' -> verbosity=2, deadline=None, max_examples=50, stateful_step_count=10, report_multiple_bugs=False, database=DirectoryBasedExampleDatabase(PosixPath('/home/mohamad/.brownie/hypothesis')) rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 2 items Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py::test_deploy PASSED tests/test_simple_storage.py::test_updating_storage PASSED ====================================================== 2 passed in 2.08s ====================================================== Terminating local RPC client...

Brownie test uses the functions in the Pytest module and you can use more functions than mentioned here to be able to have all different kinds of analysis on your smart contract deployment.

What We Achieved through Testing Deploy.py Using Brownie

In this article, we’ve completed testing the deployments of the smart contracts (deploy.py script) using Brownie in more detail, so that we can see if there is any problem at all and if there is, we can analyze it in more detail. This kind of detailed test and code analysis becomes handy, especially when the code is too long and complex that we cannot so easily understand where the unexpected result originates from.

Download this Article in PDF format

metaverse

What Do We Do in Arashtad?

3D websites, 3D games, metaverses, and other types of WebGL and 3D applications are what we develop in our 3D web development team.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
ThreeJS Development

How to Interact with Smart Contracts Using Brownie

After installing and working with Ganache CLI, we are going to interact with simple storage smart contracts Using Brownie. Meaning that we want to store and retrieve a value within the contract. After doing this you will see how simple and efficient Brownie is compared to other methods in python. We are also going to use testing modules to test the functionality of the contract and its deployment.

Using Brownie to Interact with Smart Contracts

In the previous section of this tutorial, we learned how to deploy our simple storage smart contract. In this one, we are going to interact with it, meaning that we want to store a value inside the contract and retrieve it using Brownie. After doing this you will see how simply and efficiently Using Brownie to interact with smart contracts can get compared to other methods in python.

Retrieving the Stored Data:

So, to follow our deploy.py script, we have:


from Brownie import accounts, config, SimpleStorage
def deploy_simple_storage():

	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	stored_value = simple_storage.retrieve()
	print(stored_value)
	transaction = simple_storage.store(38,{"from": account})
	transaction.wait(1)
	updated_stored_value = simple_storage.retrieve()
	print(updated_stored_value)

def main():
	deploy_simple_storage()


In the above code, we first try to retrieve the stored value inside the contract. Then, we store a number in it and again retrieve the stored value. By running this code using:

brownie run scripts/deploy.py

We have:

Brownie v1.18.1 - Python development framework for Ethereum BrownieSimpleStorageProject is the active project. Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... Running 'scripts/deploy.py::main'... Transaction sent: 0xbbd21a1abc42f0d21f4651b71cddadddecf6ba99af3a31a140434950c7e36876 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 SimpleStorage.constructor confirmed Block: 1 Gas used: 334180 (2.78%) SimpleStorage deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 0 Transaction sent: 0x6bd512176120555da2d2682b4dd0256ccb8060a5c63d678d65ff4b554ffd7477 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1 SimpleStorage.store confirmed Block: 2 Gas used: 41393 (0.34%) SimpleStorage.store confirmed Block: 2 Gas used: 41393 (0.34%) 38 Terminating local RPC client…

As you can see, 2 blocks have been hashed at the end. And at first, we have nothing stored. So, the first number that has been retrieved is 0, and then after storing the number 38, we have it as the result of the retrieve.

Testing Smart Contracts Using Brownie

One of the important steps in developing a smart contract is to test our contract. Because in the main deployment of the transaction in a real-world smart contract if anything goes wrong, like spending infinitely extra ETH or mistakes like this, there will be no way to compensate for it. The tests folder is located in the main directory and is created by Brownie. We create a file named test_simple_storage.py and write:


from Brownie import SimpleStorage, accounts
def test_deploy():
	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	starting_value = simple_storage.retrieve()
	expected = 0
	assert starting_value == expected


The above script does the same as deploy.py with the difference that it tests whether the output is the expected value or not. This will help us to understand the existing bugs without spending ETH.

brownie test

Result:

Brownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 1 item Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py . [100%] ====================================================== 1 passed in 2.17s ====================================================== Terminating local RPC client…

def test_deploy():
	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	starting_value = simple_storage.retrieve()
	expected = 38
	assert starting_value == expected


brownie test Brownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 1 item Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py F [100%] ========================================================== FAILURES =========================================================== _________________________________________________________ test_deploy _________________________________________________________ def test_deploy(): account = accounts[0] simple_storage = SimpleStorage.deploy({"from": account}) starting_value = simple_storage.retrieve() expected = 38 > assert starting_value == expected E assert 0 == 38 tests/test_simple_storage.py:10: AssertionError =================================================== short test summary info =================================================== FAILED tests/test_simple_storage.py::test_deploy - assert 0 == 38 ====================================================== 1 failed in 1.83s ====================================================== Terminating local RPC client… *****

As you can see, it shows failure because we haven’t stored any value yet and the expected number should be 0 instead of 38. Now, let’s define another function for storing the expected number, retrieving and putting it into the test:


from Brownie import SimpleStorage, accounts
def test_deploy():

	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	starting_value = simple_storage.retrieve()
	expected = 38
	assert starting_value == expected

def test_updating_storage():

	account = accounts[0]
	simple_storage = SimpleStorage.deploy({"from": account})
	expected = 38
	simple_storage.store(expected,{"from":account})
	assert expected == simple_storage.retrieve()


brownie test

Result:

Brownie v1.18.1 - Python development framework for Ethereum ===================================================== test session starts ===================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/Desktop/solidity/Solidity and web3 codes/brownie_simple_storage plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 2 items Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie'... tests/test_simple_storage.py F. [100%] ========================================================== FAILURES =========================================================== _________________________________________________________ test_deploy _________________________________________________________ def test_deploy(): account = accounts[0] simple_storage = SimpleStorage.deploy({"from": account}) starting_value = simple_storage.retrieve() expected = 38 > assert starting_value == expected E assert 0 == 38 tests/test_simple_storage.py:10: AssertionError =================================================== short test summary info =================================================== FAILED tests/test_simple_storage.py::test_deploy - assert 0 == 38 ================================================= 1 failed, 1 passed in 2.07s ================================================= Terminating local RPC client…

As you can see, one test failed and the other passed. The expected result was not 38 at first but, after storing it as expected, it passed.

Last Thought on Using Brownie to Interact with Smart Contracts

In this article, we have managed to retrieve the data we stored using the deploy.py python script. We have also created test.py files in the test directory to be able to see if the smart contract and the deploy.py work correctly. The Brownie module comes with a test package called pytest that makes the developers capable of testing any part of their script using the Brownie test command.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development