Smart contracts are immutable once deployed on the blockchain, which means they can’t be modified. Therefore, any bugs or security vulnerabilities can have serious consequences, making testing an indispensable step in the development process.
In this lesson, we will discuss the Fa1_2TestFull
contract, which includes a series of tests designed to verify the functionality of our token contract.
Fa1_2TestFull
ContractThe Fa1_2TestFull
contract is a class that inherits all the functionalities from the different contracts like Admin, Pause, Fa1_2, Mint, Burn, and ChangeMetadata. It’s used to aggregate all these functionalities and perform a thorough test to ensure the contract is working as expected.
Python
class Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(self, administrator, metadata, ledger, token_metadata):
ChangeMetadata.__init__(self)
Burn.__init__(self)
Mint.__init__(self)
Fa1_2.__init__(self, metadata, ledger, token_metadata)
Pause.__init__(self)
Admin.__init__(self, administrator)
The Fa1_2TestFull
class constructor initializes all of the functionalities.
For our contract, we start by setting up the testing scenario with test accounts and the contract initialization. This is done within a test function decorated with @sp.add_test
.
Python
@sp.add_test(name="FA12")def test():
# Initialize test scenario and accounts
sc = sp.test_scenario(m)
admin = sp.test_account("Administrator")
alice = sp.test_account("Alice")
bob = sp.test_account("Robert")
# Initialize contract with some initial values
token_metadata = {
"decimals": sp.utils.bytes_of_string("18"), # Mandatory by the spec"name": sp.utils.bytes_of_string("My Great Token"), # Recommended"symbol": sp.utils.bytes_of_string("MGT"), # Recommended# Extra fields"icon": sp.utils.bytes_of_string("https://smartpy.io/static/img/logo-only.svg"),
}
contract_metadata = sp.utils.metadata_of_url("ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd")
c1 = m.Fa1_2TestFull(administrator=admin.address,metadata=contract_metadata,token_metadata=token_metadata,ledger={},)
sc += c1
We define the test accounts: Admin, Alice, and Bob. Then we initialize our contract, Fa1_2TestFull
, with some initial values. The +=
operator adds the contract to the scenario.
From now on, to help you better understand the code, the code will be on the left and a visualisation of the code line highlighted will be on the right. Example here:
The next step is to run tests, and this involves triggering different contract functions and verifying the results.
For example, to test the minting functionality, we run:
Python
sc.h2("Admin mints a few coins")
c1.mint(address=alice.address, value=12).run(sender=admin)
This line runs a test where the admin mints 12 tokens for Alice. If the function successfully mints the tokens and correctly updates Alice’s balance, then this test passes.
SmartPy provides the verify
method to ensure that a condition holds true. If the condition is not met, the test will fail. For example:
Python
c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin)
sc.verify(c1.data.metadata[""] == sp.bytes("0x00"))
Those lines verify that the contract’s metadata was correctly updated to "0x00"
. If it was not updated correctly, the test fails.
Your smart contract tests should cover all possible use cases, including edge cases and potential failures. These can include transfers exceeding a user’s balance, burning tokens when the contract is paused, etc.
For example, a failed transfer test could look like this:
Python
sc.h2("Bob tries to transfer from Alice but he doesn't have her approval")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=bob, valid=False)
Here, Bob tries to transfer 4 tokens from Alice’s account without having approval. Since this operation should fail, we set valid=False
in the run
function. If the contract correctly prevents the transfer, the test passes.
Testing is crucial in smart contract development. Given the immutable nature of blockchain, any error in a contract can have permanent and potentially costly consequences. Writing comprehensive tests ensures that all functions behave as expected, leading to robust and secure contracts.
Remember to write tests for both positive and negative cases. Positive cases validate that a function works correctly when used as intended. Negative cases ensure the contract behaves correctly in handling incorrect or unexpected inputs.
Smart contracts are immutable once deployed on the blockchain, which means they can’t be modified. Therefore, any bugs or security vulnerabilities can have serious consequences, making testing an indispensable step in the development process.
In this lesson, we will discuss the Fa1_2TestFull
contract, which includes a series of tests designed to verify the functionality of our token contract.
Fa1_2TestFull
ContractThe Fa1_2TestFull
contract is a class that inherits all the functionalities from the different contracts like Admin, Pause, Fa1_2, Mint, Burn, and ChangeMetadata. It’s used to aggregate all these functionalities and perform a thorough test to ensure the contract is working as expected.
Python
class Fa1_2TestFull(Admin, Pause, Fa1_2, Mint, Burn, ChangeMetadata):
def __init__(self, administrator, metadata, ledger, token_metadata):
ChangeMetadata.__init__(self)
Burn.__init__(self)
Mint.__init__(self)
Fa1_2.__init__(self, metadata, ledger, token_metadata)
Pause.__init__(self)
Admin.__init__(self, administrator)
The Fa1_2TestFull
class constructor initializes all of the functionalities.
For our contract, we start by setting up the testing scenario with test accounts and the contract initialization. This is done within a test function decorated with @sp.add_test
.
Python
@sp.add_test(name="FA12")def test():
# Initialize test scenario and accounts
sc = sp.test_scenario(m)
admin = sp.test_account("Administrator")
alice = sp.test_account("Alice")
bob = sp.test_account("Robert")
# Initialize contract with some initial values
token_metadata = {
"decimals": sp.utils.bytes_of_string("18"), # Mandatory by the spec"name": sp.utils.bytes_of_string("My Great Token"), # Recommended"symbol": sp.utils.bytes_of_string("MGT"), # Recommended# Extra fields"icon": sp.utils.bytes_of_string("https://smartpy.io/static/img/logo-only.svg"),
}
contract_metadata = sp.utils.metadata_of_url("ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd")
c1 = m.Fa1_2TestFull(administrator=admin.address,metadata=contract_metadata,token_metadata=token_metadata,ledger={},)
sc += c1
We define the test accounts: Admin, Alice, and Bob. Then we initialize our contract, Fa1_2TestFull
, with some initial values. The +=
operator adds the contract to the scenario.
From now on, to help you better understand the code, the code will be on the left and a visualisation of the code line highlighted will be on the right. Example here:
The next step is to run tests, and this involves triggering different contract functions and verifying the results.
For example, to test the minting functionality, we run:
Python
sc.h2("Admin mints a few coins")
c1.mint(address=alice.address, value=12).run(sender=admin)
This line runs a test where the admin mints 12 tokens for Alice. If the function successfully mints the tokens and correctly updates Alice’s balance, then this test passes.
SmartPy provides the verify
method to ensure that a condition holds true. If the condition is not met, the test will fail. For example:
Python
c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin)
sc.verify(c1.data.metadata[""] == sp.bytes("0x00"))
Those lines verify that the contract’s metadata was correctly updated to "0x00"
. If it was not updated correctly, the test fails.
Your smart contract tests should cover all possible use cases, including edge cases and potential failures. These can include transfers exceeding a user’s balance, burning tokens when the contract is paused, etc.
For example, a failed transfer test could look like this:
Python
sc.h2("Bob tries to transfer from Alice but he doesn't have her approval")
c1.transfer(from_=alice.address, to_=bob.address, value=4).run(sender=bob, valid=False)
Here, Bob tries to transfer 4 tokens from Alice’s account without having approval. Since this operation should fail, we set valid=False
in the run
function. If the contract correctly prevents the transfer, the test passes.
Testing is crucial in smart contract development. Given the immutable nature of blockchain, any error in a contract can have permanent and potentially costly consequences. Writing comprehensive tests ensures that all functions behave as expected, leading to robust and secure contracts.
Remember to write tests for both positive and negative cases. Positive cases validate that a function works correctly when used as intended. Negative cases ensure the contract behaves correctly in handling incorrect or unexpected inputs.