9 minutes
Solidity fondamentals
Remix IDE
Remix (Orange buttons dans la partie interaction avec le smart contract de Remix -> it costs gas)
Déployer son Smart Contract sur Rinkeby en sélectionnant injected Web3 et étant connecté à Metamask.
Partager un dossier local avec remix Cloud IDE:
npm install -g @remix-project/remixd remixd -s <absolute-path-to-the-shared-folder> --remix-ide https://remix.ethereum.org
Faire tourner l’IDE Remix en local
git clone https://github.com/ethereum/remix-project.git cd remix-project yarn install yarn run build:libs nx build nx serve
Deploy a first smart contract. La base
For rapid prototyping use Remix and select “Javascript VM”.
Simplest commented Contract example
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract ExampleOne { // Contract definition
uint256 myVar; // storage varible. Can only store positive integer with a max value of 256 bits
function setMyVar(uint256 _myVar) public { // Public method than can be called externally and a parameter has to be passed
myVar = _myVar;
}
}
Compile, deploy and interact with the Smart Contract still via Remix.
Second commented Contract example
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract MyContract { // Contract definition
uint256 public myVar; // storage varible. Can only store positive integer with a max value of 256 bits. The public keywork automatically generates the get Method.
constructor(uint256 _myVar) public { // Cannot be pure or view. As a normal constructor, it is automatically called. Not prefixed by function keywork. Called during contract deployment.
myVar = _myVar; // Used to pass a variable during the deployment
}
function setMyVar(uint256 _myVar) public { // Public method than can be called externally and a parameter has to be passed
myVar = _myVar;
}
function getMyVar() public view returns(uint256) {
return myVar;
}
function() public { // Anonymous function. Used when we interact with a Smart Contract but we do not specify any function. Often used to transfer Eth. This anonymous function can also be explicitely called. It is a fallback function. We should keen it as simple as possible (for gas).
myVar = 2;
}
}
Pure functions do not interact with other part of the smart contract excepted other pure methods. An example could be a method that multiples 2 numbers.
View functions are reading functions. They interact view state variables. It is a “call” not a “transact” so they are free to use on Ethereum.
Other variable types
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract MyContract { // Contract definition
uint8 internal myUint8; // Can only be called by the contract and/or other contracts within it.
int8 private myInt8; // Can only be called from the contract
string public coinName = "70 Cent";
bool isValid = true;
uint myUint256; // = uint256
// Global variables
uint blockTime = block.timestamp;
address sender = msg.sender;
// Arrays
string[] public tokenNames = ["Dogecoin", "Luna", "USDC"];
uint[5] levels = [10, 25, 50, 100, 500];
// Datetime
uint timeNow1Sec = 1 seconds;
uint timeNow1MinInSec = 1 minutes;
uint timeNow1HourInSec = 1 hours;
uint timeNow1DayInSec = 1 days;
uint public timeNow1WeekInSec = 1 weeks;
function divideInteger() public pure returns(uint8) { // The remainer of 5/2 (0.5) will be discard. The return type uint8 rounds the return value.
uint five = 5;
uint two = 2;
return five/two;
}
function wrapAround() public pure returns(uint8) { // The remainer of 5/2 (0.5) will be discard. The return type uint8 rounds the return value.
uint8 zero = 0;
zero--;
return zero; //The result won't be -1 here since the return type is uint8. The value will be 255.
}
function getBalance() public view returns(uint) { // Returns the balance in Wei.
// 1 Ether
// = 10^18 Wei
// = 10^15 KWei
// = 10^12 MWei
// = 10^9 GWei
// = 10^6 Szabo
// = 10^3 Finney
return address(this).balance;
}
function() public payable { //Payable is mandatory if we want to pass a Ether value to the smart contract after deployment
uint balance = msg.value;
}
function withdrawEverything() public { //Send back money received in Smart Contract
msg.sender.transfer(getBalance());
}
}
Modifiers
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract MyContract { // Contract definition
uint public myVarOne;
uint public myVarTwo;
bool writable;
modifier mustBeWritable() { // Modifiers can be used to avoid the require() in functions like below
require(writable);
_;
}
function setWritable(bool _writeable) {
writeable = _writeable;
}
//function updateMyVarOne(uint _myVar) public {
// require(writable);
// myVarOne = _myVar;
//}
//function updateMyVarTwo(uint _myVar) public {
// require(writable);
// myVarTwo = _myVar;
//}
function updateMyVarOne(uint _myVar) public mustBeWritable {
myVarOne = _myVar;
}
function updateMyVarTwo(uint _myVar) public mustBeWritable {
myVarTwo = _myVar;
}
}
Mappings
Simple mapping:
mapping(string => string) public tradesAccountsMap;
Nested mapping:
struct User {
address userAddress;
string firstName;
string lastName;
}
mapping(address => mapping(string => User)) private UserNestedMap;
Enum
enum cookieSecurity {NONE, LAX, SECURE}
cookieSecurity public cookie = cookieSecurity.SECURE;
Autre example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract MyContract { // Contract definition
mapping(uint => bool) public myMapping; // key value pairs. All values are false by default. (true for all 256 values)
function writeSomethingInTheMapping(uint _myInt) public {
myMapping[_myInt] = true
}
}
Structs
With structs we can define our own data types.
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract MyContract { // Contract definition
struct MyStruct {
uint timestamp;
uint counter;
}
mapping(address => MyStruct) public myMapping;
function myFunction() public {
myMapping[msg.sender].timestamp = now;
myMapping[msg.sender].counter++;
}
}
Compare 2 strings
Don’t do:
string memory coinToFind = "btc";
string[] memory myCoins = ["btc", "eth", "etc"];
for (uint i = 0; i < myCoins.length; i++) {
string memory coin = myCoins[i];
if (coinToFind == coin) {
return i;
}
}
But do:
string memory coinToFind = "btc";
string[] memory myCoins = ["btc", "eth", "etc"];
for (uint i = 0; i < myCoins.length; i++) {
string memory coin = myCoins[i];
if (keccak256(abi.encodePacked(coinToFind) == keccak256(abi.encodePacked(coin)) {
return i;
}
}
La string en Solidity n’est pas un type natif. C’est un dynamic array.
keccak256 est une fonction hash native d’Ethereum. Les fonctions permettant de manipuler les strings (get string’s length, reading and changing the character at a given location, concat two strings, extracting part of a string doivent être implémentées manuellement.
Solution moins coûteuse en terme gaz:
function memoryCompare(bytes memory a, bytes memory b) internal pure returns(bool) {
return (a.length == b.length) && (keccak256(a) == keccak256(b));
}
function stringCompare(string memory a, string memory b) internal pure returns(bool) {
return memoryCompare(bytes(a), bytes(b));
}
Héritage
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 < 0.9.0
contract mySuperContract {
uint availableSupply;
uint maxSupply;
constructor(uint _startingSupply, uint _maxSupply) {
availableSupply = _startingSupply;
maxSupply = _maxSupply;
}
}
contract myChildContract is MySuperContract {
constructor(uint _ss, uint _ms) MySuperContract(ss, ms) {}
function getAvailableSupply() public view returns (uint) {
return availableSupply;
}
}
Notary app example code
Create a file called notary.sol
.
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version
contract Notary { // Contract definition
struct MyNotaryDocument {
bytes32 checkSum; // Hash of the file
string comments;
string fileName;
bool isSet;
address setBy;
uint timestamp;
}
mapping(bytes32 => myNotaryDocument) public docMapping;
event NewDocument(bytes32 _checksum, string fileName, address indexed _setBy); // indexed so that we can search by _setBy later (only 3)
function addDocument(bytes32 _checksum, string _fileName, string comments) public {
require(!docMapping[_checksum].isSet);
docMapping[_checksum].isSet = true;
docMapping[_checksum].fileName = _fileName;
docMapping[_checksum].timestamp = now;
docMapping[_checksum].comments = _comments;
docMapping[_checksum].setBy = msg.sender;
emit NewDocument(_checksum, _fileName, msg.sender);
}
function documentSet(bytes32 _checksum) public view returns(string, uint, string, address) {
require(docMapping[_checksum].isSet);
return (docMapping[_checksum].fileName, docMapping[_checksum].timestamp, docMapping[_checksum].comments, docMapping[_checksum].setBy);
}
}
Notary smart contract unit test in JS example
Under test directory create a new file called notaryTest.js
and add the following content:
var NotaryArtifact = artifacts.require("./notary.sol");
contract("NotaryContract", function(accounts) {
it("This is my first test", function() {
return NotaryArtifact.deployed().then(function(instance) {
console.log(instance);
})
});
it("should not be able to find a hash for an unexisting document", async function() {
return NotaryArtifact.deployed().then(async function(instance) {
try {
await instance.documentSet(0x1111111111111111111111111111111111111111111111111111111111111111);
assert.fail(true, true, "Exception expected while calling documentSet with non existing hash")
} catch(error) {
if (error.message.search("revert") >= 0) {
assert.equal(error.message.search("revert") >= 0, true, "Error Message does not reflect expected Exception Message");
} else {
throw error;
}
}
})
});
it("should not be able to create the read a document", async () => {
let instance = await NotaryArtifact.deployed();
await instance.addDocument(0x2222222222222222222222222222222222222222222222222222222222222222, "test", "test");
let entry = await instance.documentSet(0x2222222222222222222222222222222222222222222222222222222222222222);
//console.log(entry)
assert.equal(entry[0], "test", "Filename should be equal to test");
assert.equal(entry[1].toNumber() >= 1, true, "Timestamp should be bigger than 1");
assert.equal(entry[2], "test", "Comment should be equal to test");
assert.equal(entry[3, accounts[0], "The transaction should have been passed by account 0");
});
});
Then execute truffle test
.
Notary smart contract unit test in Solidity example
Under test directory create a new file called notaryTest.sol
and add the following content:
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.23;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Notary.sol";
contract NotaryTest {
function testAddAndRead() public {
Notary notaryContract = Notary(DeployedAddresses.Notary());
notaryContract.addDocument(0x3333333333333333333333333333333333333333333333333333333333333333, "test", "test");
string memory fileName;
uint timestamp;
string memory comment;
address sender;
(fileName, timestamp, comment, sender) = notaryContract.documentSet(0x3333333333333333333333333333333333333333333333333333333333333333);
Assert.equal(fileName, "test", "Filename should be equal to test");
Assert.equal(sender, address(this), "The caller and the address of the smart contract creator should be the same")
}
function testException() public {
address notaryAddress = address(DeployedAddresses.Notary());
bool transactionOk = notaryAddress.call(bytes4(keccak256("documentSet(bytes32)")) "0x4444444444444444444444444444444444444444444444444444444444444444");
Assert.equal(transactionOk, false, "Transaction should fail");
}
}
Then execute truffle test
.
Truffle Framework
Truffle is useful when you work with a blockchain development team.
Installation
# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
echo "export NVM_NODEJS_ORG_MIRROR=http://nodejs.org/dist" >> ~/.bashrc
source ~/.bashrc
nvm ls-remote
# Install NodeJS
nvm install v12.22.0
nvm use v12.22.0
nvm use default v12.22.0
# Install truffle
npm i -g truffle
# Create an empty folder and create an empty project
truffle init
# Other Truffle commands
truffle compile
truffle migrate
truffle test
# Install Ganache
git clone --depth=1 https://github.com/trufflesuite/ganache.git && cd ganache
npm install
npm start
Truffle project configuration
Edit the truffle-config.js
and add the Ganache development network. So inside module.exports = {networks:{}}
add the following lines of code:
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*", // match any network
websockets: true
},
Then run truffle migrate
to deploy the basic smart contract to your dev blockchain.
Autres remarques
-
Aujourd’hui Hardhat est plus recommendé que Truffle
-
Dans Solidity et Ethereum les data sont stockées à l’intérieur des Smart Contracts. Ce n’est pas toujours le cas avec toutes les Blochains (cf: Solana).