Smart contracts are simply computer programs. The word “contract” has no legal meaning in this context.
Once deployed, the code of a smart contract cannot change. Unlike with traditional software, the only way to modify a smart contract is to deploy a new instance.
The outcome of the execution of a smart contract is the same for everyone who runs it, given the context of the transaction that initiated its execution and the state of the Ethereum blockchain at the moment of execution.
Smart contracts operate with a very limited execution context. They can access their own state, the context of the transaction that called them, and some information about the most recent blocks.
The EVM runs as a local instance on every Ethereum node, but because all instances of the EVM operate on the same initial state and produce the same final state, the system as a whole operates as a single “world computer.”
A functional (declarative) programming language, with Lisp-like syntax. It was the first high-level language for Ethereum smart contracts but is rarely used today.
A procedural (imperative) programming language with a syntax similar to Python. Can also be used to write functional (declarative) code, though it is not entirely free of side effects.
A procedural (imperative) programming language with a syntax similar to JavaScript, C++, or Java. The most popular and frequently used language for Ethereum smart contracts.
A more recently developed language, similar to Serpent and again with Python-like syntax. Intended to get closer to a pure-functional Python-like language than Serpent, but not to replace Serpent.
A newly developed language, influenced by Erlang, with explicit state transitions and without iterative flows (loops). Intended to reduce side effects and increase auditability. Very new and yet to be widely adopted.
$ sudo add-apt-repository ppa:ethereum/ethereum $ sudo apt update $ sudo apt install solc
$ solc --version solc, the solidity compiler commandline interface Version: 0.4.24+commit.e67f0147.Linux.g++
1// Our first contract is a faucet!2contractFaucet{3 4// Give out ether to anyone who asks5functionwithdraw(uintwithdraw_amount)public{6 7// Limit withdrawal amount8require(withdraw_amount<=100000000000000000);9 10// Send the amount to the address that requested it11msg.sender.transfer(withdraw_amount);12}13 14// Accept any incoming amount15function()publicpayable{}16 17}
$ solc --optimize --bin Faucet.sol ======= Faucet.sol:Faucet ======= Binary: 6060604052341561000f57600080fd5b60cf8061001d6000396000f300606060405260043610603e5 763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416 632e1a7d4d81146040575b005b3415604a57600080fd5b603e60043567016345785d8a00008111156 06357600080fd5b73ffffffffffffffffffffffffffffffffffffffff331681156108fc0282604051 600060405180830381858888f19350505050151560a057600080fd5b505600a165627a7a723058203 556d79355f2da19e773a9551e95f1ca7457f2b5fbbf4eacf7748ab59d2532130029
$ solc --abi Faucet.sol
======= Faucet.sol:Faucet =======
Contract JSON ABI
[{"constant":false,"inputs":[{"name":"withdraw_amount","type":"uint256"}], \
"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable", \
"type":"function"},{"payable":true,"stateMutability":"payable", \
"type":"fallback"}]
pragma solidity ^0.4.19;
1// Version of Solidity compiler this program was written for2pragma solidity^0.4.19;3 4// Our first contract is a faucet!5contractFaucet{6 7// Give out ether to anyone who asks8functionwithdraw(uintwithdraw_amount)public{9 10// Limit withdrawal amount11require(withdraw_amount<=100000000000000000);12 13// Send the amount to the address that requested it14msg.sender.transfer(withdraw_amount);15}16 17// Accept any incoming amount18function()publicpayable{}19 20}
bool)Boolean value, true or false, with logical operators ! (not), && (and), || (or), == (equal), and != (not equal).
int, uint)Signed (int) and unsigned (uint) integers, declared in increments of 8 bits from int8 to uint256. Without a size suffix, 256-bit quantities are used, to match the word size of the EVM.
fixed, ufixed)Fixed-point numbers, declared with (u)fixedMxN where M is the size in bits (increments of 8 up to 256) and N is the number of decimals after the point (up to 18); e.g., ufixed32x2.
A 20-byte Ethereum address. The address object has many helpful member functions, the main ones being balance (returns the account balance) and transfer (transfers ether to the account).
Fixed-size arrays of bytes, declared with bytes1 up to bytes32.
Variable-sized arrays of bytes, declared with bytes or string.
User-defined type for enumerating discrete values: enum NAME {LABEL1, LABEL 2, ...}.
An array of any type, either fixed or dynamic: uint32[][5] is a fixed-size array of five dynamic arrays of unsigned integers.
User-defined data containers for grouping variables: struct NAME {TYPE1 VARIABLE1; TYPE2 VARIABLE2; ...}.
Hash lookup tables for key ⇒ value pairs: mapping(KEY_TYPE ⇒ VALUE_TYPE) NAME.
The units seconds, minutes, hours, and days can be used as suffixes, converting to multiples of the base unit seconds.
The units wei, finney, szabo, and ether can be used as suffixes, converting to multiples of the base unit wei.
require(withdraw_amount <= 100000000000000000);
require(withdraw_amount <= 0.1 ether);
msg.senderWe’ve already used this one. It represents the address that initiated this contract call, not necessarily the originating EOA that sent the transaction. If our contract was called directly by an EOA transaction, then this is the address that signed the transaction, but otherwise it will be a contract address.
msg.valueThe value of ether sent with this call (in wei).
msg.gasThe amount of gas left in the gas supply of this execution environment. This was deprecated in Solidity v0.4.21 and replaced by the gasleft function.
msg.dataThe data payload of this call into our contract.
msg.sigThe first four bytes of the data payload, which is the function selector.
tx.gaspriceThe gas price in the calling transaction.
tx.originThe address of the originating EOA for this transaction. WARNING: unsafe!
block.blockhash(blockNumber)The block hash of the specified block number, up to 256 blocks in the past. Deprecated and replaced with the blockhash function in Solidity v0.4.22.
block.coinbaseThe address of the recipient of the current block’s fees and block reward.
block.difficultyThe difficulty (proof of work) of the current block.
block.gaslimitThe maximum amount of gas that can be spent across all transactions included in the current block.
block.numberThe current block number (blockchain height).
block.timestampThe timestamp placed in the current block by the miner (number of seconds since the Unix epoch).
address.balanceThe balance of the address, in wei. For example, the current contract balance is address(this).balance.
address.transfer(amount)Transfers the amount (in wei) to this address, throwing an exception on any error. We used this function in our Faucet example as a method on the msg.sender address, as msg.sender.transfer.
address.send(amount)Similar to transfer, only instead of throwing an exception, it returns false on error. WARNING: always check the return value of send.
address.call(payload)Low-level CALL function—can construct an arbitrary message call with a data payload. Returns false on error. WARNING: unsafe—recipient can (accidentally or maliciously) use up all your gas, causing your contract to halt with an OOG exception; always check the return value of call.
address.callcode(payload)Low-level CALLCODE function, like address(this).call(...) but with this contract’s code replaced with that of address. Returns false on error. WARNING: advanced use only!
address.delegatecall()Low-level DELEGATECALL function, like callcode(...) but with the full msg context seen by the current contract. Returns false on error. WARNING: advanced use only!
addmod, mulmodFor modulo addition and multiplication. For example, addmod(x,y,k) calculates (x + y) % k.
keccak256, sha256, sha3, ripemd160Functions to calculate hashes with various standard hash algorithms.
ecrecoverRecovers the address used to sign a message from the signature.
selfdestrunct(recipient_address)Deletes the current contract, sending any remaining ether in the account to the recipient address.
thisinterfaceAn interface definition is structured exactly like a contract, except none of the functions are defined, they are only declared. This type of declaration is often called a stub; it tells you the functions’ arguments and return types without any implementation. An interface specifies the “shape” of a contract; when inherited, each of the functions declared by the interface must be defined by the child.
libraryA library contract is one that is meant to be deployed only once and used by other contracts, using the delegatecall method (see “address object”).
function FunctionName([parameters]) {public|private|internal|external}
[pure|constant|view|payable] [modifiers] [returns (return types)]
FunctionNameThe name of the function, which is used to call the function in a transaction (from an EOA), from another contract, or even from within the same contract. One function in each contract may be defined without a name, in which case it is the fallback function, which is called when no other function is named. The fallback function cannot have any arguments or return anything.
parametersFollowing the name, we specify the arguments that must be passed to the function, with their names and types. In our Faucet example we defined uint withdraw_amount as the only argument to the withdraw function.
publicPublic is the default; such functions can be called by other contracts or EOA transactions, or from within the contract. In our Faucet example, both functions are defined as public.
externalExternal functions are like public functions, except they cannot be called from within the contract unless explicitly prefixed with the keyword this.
internalInternal functions are only accessible from within the contract—they cannot be called by another contract or EOA transaction. They can be called by derived contracts (those that inherit this one).
privatePrivate functions are like internal functions but cannot be called by derived contracts.
constant or viewA function marked as a view promises not to modify any state. The term constant is an alias for view that will be deprecated in a future release. At this time, the compiler does not enforce the view modifier, only producing a warning, but this is expected to become an enforced keyword in v0.5 of Solidity.
pureA pure function is one that neither reads nor writes any variables in storage. It can only operate on arguments and return data, without reference to any stored data. Pure functions are intended to encourage declarative-style programming without side effects or state.
payableA payable function is one that can accept incoming payments. Functions not declared as payable will reject incoming payments. There are two exceptions, due to design decisions in the EVM: coinbase payments and SELFDESTRUCT inheritance will be paid even if the fallback function is not declared as payable, but this makes sense because code execution is not part of those payments anyway.
contractMEContract{functionMEContract(){// This is the constructor}}
pragma^0.4.22contractMEContract{constructor(){// This is the constructor}}
selfdestruct(addressrecipient);
// Version of Solidity compiler this program was written forpragma solidity^0.4.22;// Our first contract is a faucet!contractFaucet{addressowner;// Initialize Faucet contract: set ownerconstructor(){owner=msg.sender;}[...]
// Contract destructorfunctiondestroy()public{require(msg.sender==owner);selfdestruct(owner);}
modifieronlyOwner{require(msg.sender==owner);_;}
functiondestroy()publiconlyOwner{selfdestruct(owner);}
contractChildisParent{...}
contractChildisParent1,Parent2{...}
contractowned{addressowner;// Contract constructor: set ownerconstructor(){owner=msg.sender;}// Access control modifiermodifieronlyOwner{require(msg.sender==owner);_;}}
contractmortalisowned{// Contract destructorfunctiondestroy()publiconlyOwner{selfdestruct(owner);}}
contractFaucetismortal{// Give out ether to anyone who asksfunctionwithdraw(uintwithdraw_amount)public{// Limit withdrawal amountrequire(withdraw_amount<=0.1ether);// Send the amount to the address that requested itmsg.sender.transfer(withdraw_amount);}// Accept any incoming amountfunction()publicpayable{}}
require(msg.sender==owner);
require(msg.sender==owner,"Only the contract owner can call this function");
msg.sender.transfer(withdraw_amount);
require(this.balance>=withdraw_amount,"Insufficient balance in faucet for withdrawal request");msg.sender.transfer(withdraw_amount);
contractFaucetismortal{eventWithdrawal(addressindexedto,uintamount);eventDeposit(addressindexedfrom,uintamount);[...]}
// Give out ether to anyone who asksfunctionwithdraw(uintwithdraw_amount)public{[...]msg.sender.transfer(withdraw_amount);emitWithdrawal(msg.sender,withdraw_amount);}// Accept any incoming amountfunction()publicpayable{emitDeposit(msg.sender,msg.value);}
1// Version of Solidity compiler this program was written for2pragma solidity^0.4.22;3 4contractowned{5addressowner;6// Contract constructor: set owner7constructor(){8owner=msg.sender;9}10// Access control modifier11modifieronlyOwner{12require(msg.sender==owner,13"Only the contract owner can call this function");14_;15}16}17 18contractmortalisowned{19// Contract destructor20functiondestroy()publiconlyOwner{21selfdestruct(owner);22}23}24 25contractFaucetismortal{26eventWithdrawal(addressindexedto,uintamount);27eventDeposit(addressindexedfrom,uintamount);28 29// Give out ether to anyone who asks30functionwithdraw(uintwithdraw_amount)public{31// Limit withdrawal amount32require(withdraw_amount<=0.1ether);33require(this.balance>=withdraw_amount,34"Insufficient balance in faucet for withdrawal request");35// Send the amount to the address that requested it36msg.sender.transfer(withdraw_amount);37emitWithdrawal(msg.sender,withdraw_amount);38}39// Accept any incoming amount40function()publicpayable{41emitDeposit(msg.sender,msg.value);42}43}
$ truffle develop
truffle(develop)> compile
truffle(develop)> migrate
Using network 'develop'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0xb77ceae7c3f5afb7fbe3a6c5974d352aa844f53f955ee7d707ef6f3f8e6b4e61
Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Faucet...
... 0xfa850d754314c3fb83f43ca1fa6ee20bc9652d891c00a2f63fd43ab5bfb0d781
Faucet: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...
truffle(develop)> Faucet.deployed().then(i => {FaucetDeployed = i})
truffle(develop)> FaucetDeployed.send(web3.toWei(1, "ether")).then(res => \
{ console.log(res.logs[0].event, res.logs[0].args) })
Deposit { from: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
amount: BigNumber { s: 1, e: 18, c: [ 10000 ] } }
truffle(develop)> FaucetDeployed.withdraw(web3.toWei(0.1, "ether")).then(res => \
{ console.log(res.logs[0].event, res.logs[0].args) })
Withdrawal { to: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
amount: BigNumber { s: 1, e: 17, c: [ 1000 ] } }
Deposit { from: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
amount: BigNumber { s: 1, e: 18, c: [ 10000 ] } }
Withdrawal { to: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
amount: BigNumber { s: 1, e: 17, c: [ 1000 ] } }
contractTokenismortal{Faucet_faucet;constructor(){_faucet=newFaucet();}}
import"Faucet.sol";contractTokenismortal{Faucet_faucet;constructor(){_faucet=newFaucet();}}
import"Faucet.sol";contractTokenismortal{Faucet_faucet;constructor(){_faucet=(newFaucet).value(0.5ether)();}}
import"Faucet.sol";contractTokenismortal{Faucet_faucet;constructor(){_faucet=(newFaucet).value(0.5ether)();}functiondestroy()ownerOnly{_faucet.destroy();}}
import"Faucet.sol";contractTokenismortal{Faucet_faucet;constructor(address_f){_faucet=Faucet(_f);_faucet.withdraw(0.1ether)}}
contractTokenismortal{constructor(address_faucet){_faucet.call("withdraw",0.1ether);}}
contractTokenismortal{constructor(address_faucet){if!(_faucet.call("withdraw",0.1ether)){revert("Withdrawal from faucet failed");}}}
1pragma solidity^0.4.22;2 3contractcalledContract{4eventcallEvent(addresssender,addressorigin,addressfrom);5functioncalledFunction()public{6emitcallEvent(msg.sender,tx.origin,this);7}8}9 10librarycalledLibrary{11eventcallEvent(addresssender,addressorigin,addressfrom);12functioncalledFunction()public{13emitcallEvent(msg.sender,tx.origin,this);14}15}16 17contractcaller{18 19functionmake_calls(calledContract_calledContract)public{20 21// Calling calledContract and calledLibrary directly22_calledContract.calledFunction();23calledLibrary.calledFunction();24 25// Low-level calls using the address object for calledContract26require(address(_calledContract).27call(bytes4(keccak256("calledFunction()"))));28require(address(_calledContract).29delegatecall(bytes4(keccak256("calledFunction()"))));30 31 32 33}34}
truffle(develop)> migrate
Using network 'develop'.
[...]
Saving artifacts...
truffle(develop)> web3.eth.accounts[0]
'0x627306090abab3a6e1400e9345bc60c78a8bef57'
truffle(develop)> caller.address
'0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f'
truffle(develop)> calledContract.address
'0x345ca3e014aaf5dca488057592ee47305d9b3e10'
truffle(develop)> calledLibrary.address
'0xf25186b5081ff5ce73482ad761db0eb0d25abfbf'
truffle(develop)> caller.deployed().then( i => { callerDeployed = i })
truffle(develop)> callerDeployed.make_calls(calledContract.address).then(res => \
{ res.logs.forEach( log => { console.log(log.args) })})
{ sender: '0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f',
origin: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
from: '0x345ca3e014aaf5dca488057592ee47305d9b3e10' }
{ sender: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
origin: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
from: '0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f' }
{ sender: '0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f',
origin: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
from: '0x345ca3e014aaf5dca488057592ee47305d9b3e10' }
{ sender: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
origin: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
from: '0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f' }
_calledContract.calledFunction();
sender: '0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f', origin: '0x627306090abab3a6e1400e9345bc60c78a8bef57', from: '0x345ca3e014aaf5dca488057592ee47305d9b3e10'
calledLibrary.calledFunction();
sender: '0x627306090abab3a6e1400e9345bc60c78a8bef57', origin: '0x627306090abab3a6e1400e9345bc60c78a8bef57', from: '0x8f0483125fcb9aaaefa9209d8e9d7b9c8b9fb90f'
An “out of gas” exception is thrown.
The state of the contract prior to execution is restored (reverted).
All ether used to pay for the gas is taken as a transaction fee; it is not refunded.
varcontract=web3.eth.contract(abi).at(address);vargasEstimate=contract.myAweSomeMethod.estimateGas(arg1,arg2,{from:account});
vargasPrice=web3.eth.getGasPrice();
vargasCostInEther=web3.fromWei((gasEstimate*gasPrice),'ether');
varFaucetContract=artifacts.require("./Faucet.sol");FaucetContract.web3.eth.getGasPrice(function(error,result){vargasPrice=Number(result);console.log("Gas Price is "+gasPrice+" wei");// "10000000000000"// Get the contract instanceFaucetContract.deployed().then(function(FaucetContractInstance){// Use the keyword 'estimateGas' after the function name to get the gas// estimation for this particular function (aprove)FaucetContractInstance.send(web3.toWei(1,"ether"));returnFaucetContractInstance.withdraw.estimateGas(web3.toWei(0.1,"ether"));}).then(function(result){vargas=Number(result);console.log("gas estimation = "+gas+" units");console.log("gas cost estimation = "+(gas*gasPrice)+" wei");console.log("gas cost estimation = "+FaucetContract.web3.fromWei((gas*gasPrice),'ether')+" ether");});});
$ truffle develop truffle(develop)> exec gas_estimates.js Using network 'develop'. Gas Price is 20000000000 wei gas estimation = 31397 units gas cost estimation = 627940000000000 wei gas cost estimation = 0.00062794 ether