03 - Writing Smart Contracts in Solidity
Follow along in Remix IDE. Copy and paste the listings, deploy and test your solutions to the exercises.
Essential Syntax
Listing
|
|
Concepts
Executing code on the blockchain costs money, and it’s paid for in Ethereum. The network defines a unit called gas, which is set to some amount of ETH. Executing code on the Ethereum network takes some amount of gas. For example, a regular old ETH transfer transaction takes 21,000 gas. The more complex a contract’s logic, the more gas it will take, and therefore, the more expensive it will be to call that contract.
Some general rules to keep your gas usage low:
- Only compute what you absolutely have to
- Storage I/O is expensive, memory less so
- Be very careful when writing looping or recursive logic
Some recent values for example:
Gas price: 66 Gwei (0.000000066 ETH)
Gas for transaction: 21,000
Transaction fee: 21,000 × 0.000000066 ETH = 0.001386 ETH ($3.82 @ $2,756.35/ETH)
Exercises
- Create a
gift(address, uint256)
function that adds an amount to anyone’s balance, and rewards the gifter with a call to themint()
function- Make sure to update
totalSupply
. - What modifiers should the function have?
- Make sure to update
- The transfer function could use less gas while having the same functionality. What improvements can you make?
- Implementing these two optimizations improved the execution cost of the function from 28847 gas to 12777 gas (55.7% improvement).
- Optimization 1:
- Involves reading from storage
- Hint 1
- Optimization 2:
- If you need help, check out this transfer function from an OpenZeppelin ERC-20 implementation.
Payable Functions
Listing
|
|
Concepts
Payable functions are able to receive ETH tokens. The payment value is in the msg.value
global in units of wei, the smallest division of Ethereum (1 ether = $10^{18}$ wei).
If you are short on gas and your logic can be performed off-chain somewhere, events are a relatively cheap (although not free) way to integrate off-chain logic into an application. Web3 applications can listen for events and react to them.
Exercises
- Create a
buyOut()
function which changes the owner tomsg.sender
if(2 ** (2 + totalSupply)) * 1 ether
tokens are attached.- Implement the buyout value as a pure function.
- Make sure to handle payouts to the old owner correctly!
- Emit a (new)
BuyOutEvent
with the old and new owner indexed, and the buyout price. address(this).balance
is increased bymsg.value
before execution begins.
- Read this section in the Solidity documentation. Rewrite
buyOut()
with these considerations.
Design Patterns & Security Considerations
Listing
|
|
Concepts
Fallback & Receive Functions
Recall that any given Ethereum address may be controlled by a smart contract or a real person. Smart contracts can also respond to transactions sent directly to their address by using a special fallback
or receive
function. These functions look like this:
// No function keyword
receive () external payable {
// Called when the contract address is paid directly
// Just like any other payable function
// Keep in mind the gas limit could be as low as 2300
}
// Optionally payable
fallback () external payable {
// Called when no function signature matches
}
See the documentation for fallback functions and receive functions.
Cross-contract calls
Smart contracts can call each other if they know the address and interface of the contract they want to call. Fortunately, addresses aren’t too difficult to discover, and a contract’s interface can be easily imported into another file by using the import
keyword:
// At the top of the file
import "./InconspicuousToken.sol";
// Elsewhere in your contract...
InconspicuousToken it = InconspicuousToken(contractAddress);
it.buy{ value: /* ... */ }();
it.sell();
See the documentation for import and more information about calling other contracts.
Security Vulnerability: Re-entrancy
Sending ETH to an address has the potential to trigger some smart contract code—smart contract code which could theoretically call other smart contract code and so on and so forth.
This all brings us to a type of security vulnerability called re-entrancy, and as it turns out, the above listing is vulnerable to a re-entrancy attack.
The general idea of a re-entrancy attack is to call back into a contract while it is in the middle of executing (i.e. when it calls another contract), in order to take advantage of an invalid intermediate state.
Mitigation
Use the checks-effects-interactions1 order of operations:
- Check & validate your input
- Perform any and all modifications to contract state
- Send calls to other contracts
This ensures that your contract state will always be valid when other contracts may be attempting to interact with it even as part of one of your own function calls.
Exercises
- Construct a contract that performs a re-entrancy attack on
InconspicuousToken
.- Your contract should contain four functions:
contractBalance()
for figuring out if your attack worked- A
receive
function performAttack1(address)
which performs a buy onInconspicuousToken
performAttack2(address)
which performs a sell onInconspicuousToken
- The attack should drain the
InconspicuousToken
’s balance into the attacking contract’s balance
- Your contract should contain four functions:
- Use the check-effects-interactions pattern to fix
InconspicuousToken
.
Project Status Update
Begin writing the smart contract(s) for your project. Do not be afraid to use sources like the OpenZeppelin contract library for help and inspiration. Remember to emit events for things that your web application will need to be aware of!