-
-
Save erbrito/e301e8b8ca18f264bef714c50a167d12 to your computer and use it in GitHub Desktop.
pragma solidity ^0.4.16; | |
contract owned { | |
address public owner; | |
function owned() public { | |
owner = msg.sender; | |
} | |
modifier onlyOwner { | |
require(msg.sender == owner); | |
_; | |
} | |
function transferOwnership(address newOwner) onlyOwner public { | |
owner = newOwner; | |
} | |
} | |
contract tokenRecipient { | |
event receivedEther(address sender, uint amount); | |
event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData); | |
function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public { | |
Token t = Token(_token); | |
require(t.transferFrom(_from, this, _value)); | |
receivedTokens(_from, _value, _token, _extraData); | |
} | |
function () payable public { | |
receivedEther(msg.sender, msg.value); | |
} | |
} | |
interface Token { | |
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); | |
} | |
contract Congress is owned, tokenRecipient { | |
// Contract Variables and events | |
uint public minimumQuorum; | |
uint public debatingPeriodInMinutes; | |
int public majorityMargin; | |
Proposal[] public proposals; | |
uint public numProposals; | |
mapping (address => uint) public memberId; | |
Member[] public members; | |
event ProposalAdded(uint proposalID, address recipient, uint amount, string description); | |
event Voted(uint proposalID, bool position, address voter, string justification); | |
event ProposalTallied(uint proposalID, int result, uint quorum, bool active); | |
event MembershipChanged(address member, bool isMember); | |
event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, int newMajorityMargin); | |
struct Proposal { | |
address recipient; | |
uint amount; | |
string description; | |
uint votingDeadline; | |
bool executed; | |
bool proposalPassed; | |
uint numberOfVotes; | |
int currentResult; | |
bytes32 proposalHash; | |
Vote[] votes; | |
mapping (address => bool) voted; | |
} | |
struct Member { | |
address member; | |
string name; | |
uint memberSince; | |
} | |
struct Vote { | |
bool inSupport; | |
address voter; | |
string justification; | |
} | |
// Modifier that allows only shareholders to vote and create new proposals | |
modifier onlyMembers { | |
require(memberId[msg.sender] != 0); | |
_; | |
} | |
/** | |
* Constructor function | |
*/ | |
function Congress ( | |
uint minimumQuorumForProposals, | |
uint minutesForDebate, | |
int marginOfVotesForMajority | |
) payable public { | |
changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority); | |
// It’s necessary to add an empty first member | |
addMember(0, ""); | |
// and let's add the founder, to save a step later | |
addMember(owner, 'founder'); | |
} | |
/** | |
* Add member | |
* | |
* Make `targetMember` a member named `memberName` | |
* | |
* @param targetMember ethereum address to be added | |
* @param memberName public name for that member | |
*/ | |
function addMember(address targetMember, string memberName) onlyOwner public { | |
uint id = memberId[targetMember]; | |
if (id == 0) { | |
memberId[targetMember] = members.length; | |
id = members.length++; | |
} | |
members[id] = Member({member: targetMember, memberSince: now, name: memberName}); | |
MembershipChanged(targetMember, true); | |
} | |
/** | |
* Remove member | |
* | |
* @notice Remove membership from `targetMember` | |
* | |
* @param targetMember ethereum address to be removed | |
*/ | |
function removeMember(address targetMember) onlyOwner public { | |
require(memberId[targetMember] != 0); | |
for (uint i = memberId[targetMember]; i<members.length-1; i++){ | |
members[i] = members[i+1]; | |
} | |
delete members[members.length-1]; | |
members.length--; | |
} | |
/** | |
* Change voting rules | |
* | |
* Make so that proposals need to be discussed for at least `minutesForDebate/60` hours, | |
* have at least `minimumQuorumForProposals` votes, and have 50% + `marginOfVotesForMajority` votes to be executed | |
* | |
* @param minimumQuorumForProposals how many members must vote on a proposal for it to be executed | |
* @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed | |
* @param marginOfVotesForMajority the proposal needs to have 50% plus this number | |
*/ | |
function changeVotingRules( | |
uint minimumQuorumForProposals, | |
uint minutesForDebate, | |
int marginOfVotesForMajority | |
) onlyOwner public { | |
minimumQuorum = minimumQuorumForProposals; | |
debatingPeriodInMinutes = minutesForDebate; | |
majorityMargin = marginOfVotesForMajority; | |
ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, majorityMargin); | |
} | |
/** | |
* Add Proposal | |
* | |
* Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code. | |
* | |
* @param beneficiary who to send the ether to | |
* @param weiAmount amount of ether to send, in wei | |
* @param jobDescription Description of job | |
* @param transactionBytecode bytecode of transaction | |
*/ | |
function newProposal( | |
address beneficiary, | |
uint weiAmount, | |
string jobDescription, | |
bytes transactionBytecode | |
) | |
onlyMembers public | |
returns (uint proposalID) | |
{ | |
proposalID = proposals.length++; | |
Proposal storage p = proposals[proposalID]; | |
p.recipient = beneficiary; | |
p.amount = weiAmount; | |
p.description = jobDescription; | |
p.proposalHash = keccak256(beneficiary, weiAmount, transactionBytecode); | |
p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes; | |
p.executed = false; | |
p.proposalPassed = false; | |
p.numberOfVotes = 0; | |
ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription); | |
numProposals = proposalID+1; | |
return proposalID; | |
} | |
/** | |
* Add proposal in Ether | |
* | |
* Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code. | |
* This is a convenience function to use if the amount to be given is in round number of ether units. | |
* | |
* @param beneficiary who to send the ether to | |
* @param etherAmount amount of ether to send | |
* @param jobDescription Description of job | |
* @param transactionBytecode bytecode of transaction | |
*/ | |
function newProposalInEther( | |
address beneficiary, | |
uint etherAmount, | |
string jobDescription, | |
bytes transactionBytecode | |
) | |
onlyMembers public | |
returns (uint proposalID) | |
{ | |
return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode); | |
} | |
/** | |
* Check if a proposal code matches | |
* | |
* @param proposalNumber ID number of the proposal to query | |
* @param beneficiary who to send the ether to | |
* @param weiAmount amount of ether to send | |
* @param transactionBytecode bytecode of transaction | |
*/ | |
function checkProposalCode( | |
uint proposalNumber, | |
address beneficiary, | |
uint weiAmount, | |
bytes transactionBytecode | |
) | |
constant public | |
returns (bool codeChecksOut) | |
{ | |
Proposal storage p = proposals[proposalNumber]; | |
return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode); | |
} | |
/** | |
* Log a vote for a proposal | |
* | |
* Vote `supportsProposal? in support of : against` proposal #`proposalNumber` | |
* | |
* @param proposalNumber number of proposal | |
* @param supportsProposal either in favor or against it | |
* @param justificationText optional justification text | |
*/ | |
function vote( | |
uint proposalNumber, | |
bool supportsProposal, | |
string justificationText | |
) | |
onlyMembers public | |
returns (uint voteID) | |
{ | |
Proposal storage p = proposals[proposalNumber]; // Get the proposal | |
require(!p.voted[msg.sender]); // If has already voted, cancel | |
p.voted[msg.sender] = true; // Set this voter as having voted | |
p.numberOfVotes++; // Increase the number of votes | |
if (supportsProposal) { // If they support the proposal | |
p.currentResult++; // Increase score | |
} else { // If they don't | |
p.currentResult--; // Decrease the score | |
} | |
// Create a log of this event | |
Voted(proposalNumber, supportsProposal, msg.sender, justificationText); | |
return p.numberOfVotes; | |
} | |
/** | |
* Finish vote | |
* | |
* Count the votes proposal #`proposalNumber` and execute it if approved | |
* | |
* @param proposalNumber proposal number | |
* @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it | |
*/ | |
function executeProposal(uint proposalNumber, bytes transactionBytecode) public { | |
Proposal storage p = proposals[proposalNumber]; | |
require(now > p.votingDeadline // If it is past the voting deadline | |
&& !p.executed // and it has not already been executed | |
&& p.proposalHash == keccak256(p.recipient, p.amount, transactionBytecode) // and the supplied code matches the proposal | |
&& p.numberOfVotes >= minimumQuorum); // and a minimum quorum has been reached... | |
// ...then execute result | |
if (p.currentResult > majorityMargin) { | |
// Proposal passed; execute the transaction | |
p.executed = true; // Avoid recursive calling | |
require(p.recipient.call.value(p.amount)(transactionBytecode)); | |
p.proposalPassed = true; | |
} else { | |
// Proposal failed | |
p.proposalPassed = false; | |
} | |
// Fire Events | |
ProposalTallied(proposalNumber, p.currentResult, p.numberOfVotes, p.proposalPassed); | |
} | |
} |
pragma solidity ^0.4.16; | |
interface token { | |
function transfer(address receiver, uint amount); | |
} | |
contract Crowdsale { | |
address public beneficiary; | |
uint public fundingGoal; | |
uint public amountRaised; | |
uint public deadline; | |
uint public price; | |
token public tokenReward; | |
mapping(address => uint256) public balanceOf; | |
bool fundingGoalReached = false; | |
bool crowdsaleClosed = false; | |
event GoalReached(address recipient, uint totalAmountRaised); | |
event FundTransfer(address backer, uint amount, bool isContribution); | |
/** | |
* Constructor function | |
* | |
* Setup the owner | |
*/ | |
function Crowdsale( | |
address ifSuccessfulSendTo, | |
uint fundingGoalInEthers, | |
uint durationInMinutes, | |
uint etherCostOfEachToken, | |
address addressOfTokenUsedAsReward | |
) { | |
beneficiary = ifSuccessfulSendTo; | |
fundingGoal = fundingGoalInEthers * 1 ether; | |
deadline = now + durationInMinutes * 1 minutes; | |
price = etherCostOfEachToken * 1 ether; | |
tokenReward = token(addressOfTokenUsedAsReward); | |
} | |
/** | |
* Fallback function | |
* | |
* The function without name is the default function that is called whenever anyone sends funds to a contract | |
*/ | |
function () payable { | |
require(!crowdsaleClosed); | |
uint amount = msg.value; | |
balanceOf[msg.sender] += amount; | |
amountRaised += amount; | |
tokenReward.transfer(msg.sender, amount / price); | |
FundTransfer(msg.sender, amount, true); | |
} | |
modifier afterDeadline() { if (now >= deadline) _; } | |
/** | |
* Check if goal was reached | |
* | |
* Checks if the goal or time limit has been reached and ends the campaign | |
*/ | |
function checkGoalReached() afterDeadline { | |
if (amountRaised >= fundingGoal){ | |
fundingGoalReached = true; | |
GoalReached(beneficiary, amountRaised); | |
} | |
crowdsaleClosed = true; | |
} | |
/** | |
* Withdraw the funds | |
* | |
* Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached, | |
* sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw | |
* the amount they contributed. | |
*/ | |
function safeWithdrawal() afterDeadline { | |
if (!fundingGoalReached) { | |
uint amount = balanceOf[msg.sender]; | |
balanceOf[msg.sender] = 0; | |
if (amount > 0) { | |
if (msg.sender.send(amount)) { | |
FundTransfer(msg.sender, amount, false); | |
} else { | |
balanceOf[msg.sender] = amount; | |
} | |
} | |
} | |
if (fundingGoalReached && beneficiary == msg.sender) { | |
if (beneficiary.send(amountRaised)) { | |
FundTransfer(beneficiary, amountRaised, false); | |
} else { | |
//If we fail to send the funds to beneficiary, unlock funders balance | |
fundingGoalReached = false; | |
} | |
} | |
} | |
} |
# add contract to file ./contracts/ContractName.sol | |
npm install zeppelin-solidity | |
--- | |
pragma solidity ^0.4.17; | |
import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol'; | |
contract TutorialToken is StandardToken { | |
--- | |
truffle compile | |
vi migrations/2_deploy_contracts.js | |
---- | |
var TokenERC20 = artifacts.require("TokenERC20"); | |
module.exports = function(deployer) { | |
deployer.deploy(TokenERC20, 100, "Gadgets", "(:package:)"); | |
}; | |
---- | |
truffle migrate |
// bad | |
if(!someAddress.call.value(100)()) { | |
// Some failure code | |
} | |
// good | |
if(!someAddress.send(100)) { | |
// Some failure code | |
} | |
// bad | |
someAddress.send(55); | |
someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result | |
someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted | |
// good | |
if(!someAddress.send(55)) { | |
// Some failure code | |
} | |
ExternalContract(someAddress).deposit.value(100); | |
// bad | |
contract auction { | |
address highestBidder; | |
uint highestBid; | |
function bid() { | |
if (msg.value < highestBid) throw; | |
if (highestBidder != 0) { | |
if (!highestBidder.send(highestBid)) { // if this call consistently fails, no one else can bid | |
throw; | |
} | |
} | |
highestBidder = msg.sender; | |
highestBid = msg.value; | |
} | |
} | |
// good | |
contract auction { | |
address highestBidder; | |
uint highestBid; | |
mapping(address => uint) refunds; | |
function bid() external { | |
if (msg.value < highestBid) throw; | |
if (highestBidder != 0) { | |
refunds[highestBidder] += highestBid; // record the refund that this user can claim | |
} | |
highestBidder = msg.sender; | |
highestBid = msg.value; | |
} | |
function withdrawRefund() external { | |
uint refund = refunds[msg.sender]; | |
refunds[msg.sender] = 0; | |
if (!msg.sender.send(refund)) { | |
refunds[msg.sender] = refund; // reverting state because send failed | |
} | |
} | |
} | |
// bad | |
Bank.withdraw(100); // Unclear whether trusted or untrusted | |
function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe | |
UntrustedBank.withdraw(amount); | |
} | |
// good | |
UntrustedBank.withdraw(100); // untrusted external call | |
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp | |
function makeUntrustedWithdrawal(uint amount) { | |
UntrustedBank.withdraw(amount); | |
} | |
// bad | |
uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer | |
// good | |
uint multiplier = 10; | |
uint x = (5 * multiplier) / 2; | |
uint numerator = 5; | |
uint denominator = 2; | |
// bad | |
function() { balances[msg.sender] += msg.value; } | |
// good | |
function() { throw; } | |
function deposit() external { balances[msg.sender] += msg.value; } | |
function() { LogDepositReceived(msg.sender); } | |
// bad | |
uint x; // the default is private for state variables, but it should be made explicit | |
function transfer() { // the default is public | |
// public code | |
} | |
// good | |
uint private y; | |
function transfer() public { | |
// public code | |
} | |
function internalAction() internal { | |
// internal code | |
} | |
// bad | |
function divide(uint x, uint y) returns(uint) { | |
return x / y; | |
} | |
// good | |
function divide(uint x, uint y) returns(uint) { | |
if (y == 0) { throw; } | |
return x / y; | |
} | |
// bad | |
event Transfer() {} | |
function transfer() {} | |
// good | |
event LogTransfer() {} | |
function transfer() external {} | |
pragma solidity ^0.4.11; | |
contract StateMachine { | |
enum Stages { | |
AcceptingBlindedBids, | |
RevealBids, | |
AnotherStage, | |
AreWeDoneYet, | |
Finished | |
} | |
// This is the current stage. | |
Stages public stage = Stages.AcceptingBlindedBids; | |
uint public creationTime = now; | |
modifier atStage(Stages _stage) { | |
require(stage == _stage); | |
_; | |
} | |
function nextStage() internal { | |
stage = Stages(uint(stage) + 1); | |
} | |
// Perform timed transitions. Be sure to mention | |
// this modifier first, otherwise the guards | |
// will not take the new stage into account. | |
modifier timedTransitions() { | |
if (stage == Stages.AcceptingBlindedBids && | |
now >= creationTime + 10 days) | |
nextStage(); | |
if (stage == Stages.RevealBids && | |
now >= creationTime + 12 days) | |
nextStage(); | |
// The other stages transition by transaction | |
_; | |
} | |
// Order of the modifiers matters here! | |
function bid() | |
public | |
payable | |
timedTransitions | |
atStage(Stages.AcceptingBlindedBids) | |
{ | |
// We will not implement that here | |
} | |
function reveal() | |
public | |
timedTransitions | |
atStage(Stages.RevealBids) | |
{ | |
} | |
// This modifier goes to the next stage | |
// after the function is done. | |
modifier transitionNext() | |
{ | |
_; | |
nextStage(); | |
} | |
function g() | |
public | |
timedTransitions | |
atStage(Stages.AnotherStage) | |
transitionNext | |
{ | |
} | |
function h() | |
public | |
timedTransitions | |
atStage(Stages.AreWeDoneYet) | |
transitionNext | |
{ | |
} | |
function i() | |
public | |
timedTransitions | |
atStage(Stages.Finished) | |
{ | |
} | |
} |
pragma solidity ^0.4.16; | |
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _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 notifies clients about the amount burnt | |
event Burn(address indexed from, uint256 value); | |
/** | |
* Constructor function | |
* | |
* Initializes contract with initial supply tokens to the creator of the contract | |
*/ | |
function TokenERC20( | |
uint256 initialSupply, | |
string tokenName, | |
string 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 | |
} | |
/** | |
* Internal transfer, only can be called by this contract | |
*/ | |
function _transfer(address _from, address _to, uint _value) internal { | |
// Prevent transfer to 0x0 address. Use burn() instead | |
require(_to != 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); | |
} | |
/** | |
* Transfer tokens | |
* | |
* Send `_value` tokens to `_to` from your account | |
* | |
* @param _to The address of the recipient | |
* @param _value the amount to send | |
*/ | |
function transfer(address _to, uint256 _value) public { | |
_transfer(msg.sender, _to, _value); | |
} | |
/** | |
* Transfer tokens from other address | |
* | |
* Send `_value` tokens to `_to` on behalf of `_from` | |
* | |
* @param _from The address of the sender | |
* @param _to The address of the recipient | |
* @param _value the amount to send | |
*/ | |
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; | |
} | |
/** | |
* Set allowance for other address | |
* | |
* Allows `_spender` to spend no more than `_value` tokens on your behalf | |
* | |
* @param _spender The address authorized to spend | |
* @param _value the max amount they can spend | |
*/ | |
function approve(address _spender, uint256 _value) public | |
returns (bool success) { | |
allowance[msg.sender][_spender] = _value; | |
return true; | |
} | |
/** | |
* Set allowance for other address and notify | |
* | |
* Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it | |
* | |
* @param _spender The address authorized to spend | |
* @param _value the max amount they can spend | |
* @param _extraData some extra information to send to the approved contract | |
*/ | |
function approveAndCall(address _spender, uint256 _value, bytes _extraData) | |
public | |
returns (bool success) { | |
tokenRecipient spender = tokenRecipient(_spender); | |
if (approve(_spender, _value)) { | |
spender.receiveApproval(msg.sender, _value, this, _extraData); | |
return true; | |
} | |
} | |
/** | |
* Destroy tokens | |
* | |
* Remove `_value` tokens from the system irreversibly | |
* | |
* @param _value the amount of money to burn | |
*/ | |
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; | |
} | |
/** | |
* Destroy tokens from other account | |
* | |
* Remove `_value` tokens from the system irreversibly on behalf of `_from`. | |
* | |
* @param _from the address of the sender | |
* @param _value the amount of money to burn | |
*/ | |
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; | |
} | |
} |
https://github.com/OpenZeppelin/zeppelin-solidity
npm install -E zeppelin-solidity
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract MyContract is Ownable {
...
}
https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol
https://github.com/ethereum/wiki/wiki/Safety#recommendations-for-smart-contract-security-in-solidity
https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/payment/PullPayment.sol
'''
import './PullPayment.sol';
contract GoodArrayUse is PullPayment {
address[] employees;
mapping(address => uint) bonuses;
function payBonus() {
for (uint i = 0; i < employees.length; i++) {
address employee = employees[i];
uint bonus = bonuses[employee];
asyncSend(employee, bonus);
}
}
function calculateBonus(address employee) returns (uint) {
uint bonus = 0;
// some expensive computation...
bonuses[employee] = bonus;
}
}
'''
https://www.ethereum.org/crowdsale
https://www.ethereum.org/dao
https://www.ethereum.org/token
https://ethereum.org/dao
http://truffleframework.com/tutorials/robust-smart-contracts-with-openzeppelin
http://truffleframework.com/tutorials/pet-shop
http://truffleframework.com/ganache/
https://blog.zeppelin.solutions/onward-with-ethereum-smart-contract-security-97a827e47702