Skip to content

Instantly share code, notes, and snippets.

@danikula
Created February 28, 2021 10:14
Show Gist options
  • Save danikula/20d2d56366dc5fd7b4c22fbfeb435a30 to your computer and use it in GitHub Desktop.
Save danikula/20d2d56366dc5fd7b4c22fbfeb435a30 to your computer and use it in GitHub Desktop.
Classic Ponzi Pyramid on smart contract
pragma solidity ^0.4.26;
contract InvestorsStorage {
struct investor {
uint keyIndex;
uint value;
uint paymentTime;
uint refBonus;
}
struct itmap {
mapping(address => investor) data;
address[] keys;
}
itmap private s;
address private owner;
modifier onlyOwner() {
require(msg.sender == owner, "access denied");
_;
}
constructor() public {
owner = msg.sender;
s.keys.length++;
}
function insert(address addr, uint value) public onlyOwner returns (bool) {
uint keyIndex = s.data[addr].keyIndex;
if (keyIndex != 0) return false;
s.data[addr].value = value;
keyIndex = s.keys.length++;
s.data[addr].keyIndex = keyIndex;
s.keys[keyIndex] = addr;
return true;
}
function investorFullInfo(address addr) public view returns(uint, uint, uint, uint) {
return (
s.data[addr].keyIndex,
s.data[addr].value,
s.data[addr].paymentTime,
s.data[addr].refBonus
);
}
function investorBaseInfo(address addr) public view returns(uint, uint, uint) {
return (
s.data[addr].value,
s.data[addr].paymentTime,
s.data[addr].refBonus
);
}
function investorShortInfo(address addr) public view returns(uint, uint) {
return (
s.data[addr].value,
s.data[addr].refBonus
);
}
function addRefBonus(address addr, uint refBonus) public onlyOwner returns (bool) {
if (s.data[addr].keyIndex == 0) return false;
s.data[addr].refBonus += refBonus;
return true;
}
function addValue(address addr, uint value) public onlyOwner returns (bool) {
if (s.data[addr].keyIndex == 0) return false;
s.data[addr].value += value;
return true;
}
function setPaymentTime(address addr, uint paymentTime) public onlyOwner returns (bool) {
if (s.data[addr].keyIndex == 0) return false;
s.data[addr].paymentTime = paymentTime;
return true;
}
function setRefBonus(address addr, uint refBonus) public onlyOwner returns (bool) {
if (s.data[addr].keyIndex == 0) return false;
s.data[addr].refBonus = refBonus;
return true;
}
function keyFromIndex(uint i) public view returns (address) {
return s.keys[i];
}
function contains(address addr) public view returns (bool) {
return s.data[addr].keyIndex > 0;
}
function size() public view returns (uint) {
return s.keys.length;
}
function iterStart() public pure returns (uint) {
return 1;
}
}
library SafeMath {
function mul(uint256 _a, uint256 _b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (_a == 0) {
return 0;
}
uint256 c = _a * _b;
require(c / _a == _b);
return c;
}
function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
require(_b > 0); // Solidity only automatically asserts when dividing by 0
uint256 c = _a / _b;
// assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
require(_b <= _a);
uint256 c = _a - _b;
return c;
}
function add(uint256 _a, uint256 _b) internal pure returns (uint256) {
uint256 c = _a + _b;
require(c >= _a);
return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
library Percent {
// Solidity automatically throws when dividing by 0
struct percent {
uint num;
uint den;
}
function mul(percent storage p, uint a) internal view returns (uint) {
if (a == 0) {
return 0;
}
return a*p.num/p.den;
}
function div(percent storage p, uint a) internal view returns (uint) {
return a/p.num*p.den;
}
function sub(percent storage p, uint a) internal view returns (uint) {
uint b = mul(p, a);
if (b >= a) return 0;
return a - b;
}
function add(percent storage p, uint a) internal view returns (uint) {
return a + mul(p, a);
}
}
contract Accessibility {
enum AccessRank { None, Payout, Paymode, Full }
mapping(address => AccessRank) internal m_admins;
modifier onlyAdmin(AccessRank r) {
require(
m_admins[msg.sender] == r || m_admins[msg.sender] == AccessRank.Full,
"access denied"
);
_;
}
event LogProvideAccess(address indexed whom, uint when, AccessRank rank);
constructor() public {
m_admins[msg.sender] = AccessRank.Full;
emit LogProvideAccess(msg.sender, now, AccessRank.Full);
}
function provideAccess(address addr, AccessRank rank) public onlyAdmin(AccessRank.Full) {
require(rank <= AccessRank.Full, "invalid access rank");
require(m_admins[addr] != AccessRank.Full, "cannot change full access rank");
if (m_admins[addr] != rank) {
m_admins[addr] = rank;
emit LogProvideAccess(addr, now, rank);
}
}
function access(address addr) public view returns(AccessRank rank) {
rank = m_admins[addr];
}
}
contract PaymentSystem {
// https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls
enum Paymode { Push, Pull }
struct PaySys {
uint latestTime;
uint latestKeyIndex;
Paymode mode;
}
PaySys internal m_paysys;
modifier atPaymode(Paymode mode) {
require(m_paysys.mode == mode, "pay mode does not the same");
_;
}
event LogPaymodeChanged(uint when, Paymode indexed mode);
function paymode() public view returns(Paymode mode) {
mode = m_paysys.mode;
}
function changePaymode(Paymode mode) internal {
require(mode <= Paymode.Pull, "invalid pay mode");
if (mode == m_paysys.mode ) return;
if (mode == Paymode.Pull) require(m_paysys.latestTime != 0, "cannot set pull pay mode if latest time is 0");
if (mode == Paymode.Push) m_paysys.latestTime = 0;
m_paysys.mode = mode;
emit LogPaymodeChanged(now, m_paysys.mode);
}
}
library Zero {
function requireNotZero(uint a) internal pure {
require(a != 0, "require not zero");
}
function requireNotZero(address addr) internal pure {
require(addr != address(0), "require not zero address");
}
function notZero(address addr) internal pure returns(bool) {
return !(addr == address(0));
}
function isZero(address addr) internal pure returns(bool) {
return addr == address(0);
}
}
library ToAddress {
function toAddr(uint source) internal pure returns(address) {
return address(source);
}
function toAddr(bytes source) internal pure returns(address addr) {
assembly { addr := mload(add(source,0x14)) }
return addr;
}
}
contract Revolution is Accessibility, PaymentSystem {
using Percent for Percent.percent;
using SafeMath for uint;
using Zero for *;
using ToAddress for *;
// investors storage - iterable map;
InvestorsStorage private m_investors;
mapping(address => bool) private m_referrals;
bool private m_nextWave;
// automatically generates getters
address public adminAddr;
address public payerAddr;
uint public waveStartup;
uint public investmentsNum;
uint public constant minInvesment = 10 finney; // 0.01 eth
uint public constant maxBalance = 333e5 ether; // 33,300,000 eth
uint public constant pauseOnNextWave = 168 hours;
// percents
Percent.percent private m_dividendsPercent = Percent.percent(333, 10000); // 333/10000*100% = 3.33%
Percent.percent private m_adminPercent = Percent.percent(1, 10); // 1/10*100% = 10%
Percent.percent private m_payerPercent = Percent.percent(7, 100); // 7/100*100% = 7%
Percent.percent private m_refPercent = Percent.percent(3, 100); // 3/100*100% = 3%
// more events for easy read from blockchain
event LogNewInvestor(address indexed addr, uint when, uint value);
event LogNewInvesment(address indexed addr, uint when, uint value);
event LogNewReferral(address indexed addr, uint when, uint value);
event LogPayDividends(address indexed addr, uint when, uint value);
event LogPayReferrerBonus(address indexed addr, uint when, uint value);
event LogBalanceChanged(uint when, uint balance);
event LogAdminAddrChanged(address indexed addr, uint when);
event LogPayerAddrChanged(address indexed addr, uint when);
event LogNextWave(uint when);
modifier balanceChanged {
_;
emit LogBalanceChanged(now, address(this).balance);
}
modifier notOnPause() {
require(waveStartup+pauseOnNextWave <= now, "pause on next wave not expired");
_;
}
constructor() public {
adminAddr = msg.sender;
emit LogAdminAddrChanged(msg.sender, now);
payerAddr = msg.sender;
emit LogPayerAddrChanged(msg.sender, now);
nextWave();
waveStartup = waveStartup.sub(pauseOnNextWave);
}
function() public payable {
// investor get him dividends
if (msg.value == 0) {
getMyDividends();
return;
}
// sender do invest
address a = msg.data.toAddr();
address[3] memory refs;
if (a.notZero()) {
refs[0] = a;
doInvest(refs);
} else {
doInvest(refs);
}
}
function investorsNumber() public view returns(uint) {
return m_investors.size()-1;
// -1 because see InvestorsStorage constructor where keys.length++
}
function balanceETH() public view returns(uint) {
return address(this).balance;
}
function payerPercent() public view returns(uint numerator, uint denominator) {
(numerator, denominator) = (m_payerPercent.num, m_payerPercent.den);
}
function dividendsPercent() public view returns(uint numerator, uint denominator) {
(numerator, denominator) = (m_dividendsPercent.num, m_dividendsPercent.den);
}
function adminPercent() public view returns(uint numerator, uint denominator) {
(numerator, denominator) = (m_adminPercent.num, m_adminPercent.den);
}
function referrerPercent() public view returns(uint numerator, uint denominator) {
(numerator, denominator) = (m_refPercent.num, m_refPercent.den);
}
function investorInfo(address addr) public view returns(uint value, uint paymentTime, uint refBonus, bool isReferral) {
(value, paymentTime, refBonus) = m_investors.investorBaseInfo(addr);
isReferral = m_referrals[addr];
}
function latestPayout() public view returns(uint timestamp) {
return m_paysys.latestTime;
}
function getMyDividends() public notOnPause atPaymode(Paymode.Pull) balanceChanged {
// check investor info
InvestorsStorage.investor memory investor = getMemInvestor(msg.sender);
require(investor.keyIndex > 0, "sender is not investor");
if (investor.paymentTime < m_paysys.latestTime) {
assert(m_investors.setPaymentTime(msg.sender, m_paysys.latestTime));
investor.paymentTime = m_paysys.latestTime;
}
// calculate days after latest payment
uint256 daysAfter = now.sub(investor.paymentTime).div(24 hours);
require(daysAfter > 0, "the latest payment was earlier than 24 hours");
assert(m_investors.setPaymentTime(msg.sender, now));
// check enough eth
uint value = m_dividendsPercent.mul(investor.value) * daysAfter;
if (address(this).balance < value + investor.refBonus) {
nextWave();
return;
}
// send dividends and ref bonus
if (investor.refBonus > 0) {
assert(m_investors.setRefBonus(msg.sender, 0));
sendDividendsWithRefBonus(msg.sender, value, investor.refBonus);
} else {
sendDividends(msg.sender, value);
}
}
function doInvest(address[3] refs) public payable notOnPause balanceChanged {
require(msg.value >= minInvesment, "msg.value must be >= minInvesment");
require(address(this).balance <= maxBalance, "the contract eth balance limit");
uint value = msg.value;
// ref system works only once for sender-referral
if (!m_referrals[msg.sender]) {
// level 1
if (notZeroNotSender(refs[0]) && m_investors.contains(refs[0])) {
uint reward = m_refPercent.mul(value);
assert(m_investors.addRefBonus(refs[0], reward)); // referrer 1 bonus
m_referrals[msg.sender] = true;
value = m_dividendsPercent.add(value); // referral bonus
emit LogNewReferral(msg.sender, now, value);
// level 2
if (notZeroNotSender(refs[1]) && m_investors.contains(refs[1]) && refs[0] != refs[1]) {
assert(m_investors.addRefBonus(refs[1], reward)); // referrer 2 bonus
// level 3
if (notZeroNotSender(refs[2]) && m_investors.contains(refs[2]) && refs[0] != refs[2] && refs[1] != refs[2]) {
assert(m_investors.addRefBonus(refs[2], reward)); // referrer 3 bonus
}
}
}
}
// commission
adminAddr.transfer(m_adminPercent.mul(msg.value));
payerAddr.transfer(m_payerPercent.mul(msg.value));
// write to investors storage
if (m_investors.contains(msg.sender)) {
assert(m_investors.addValue(msg.sender, value));
} else {
assert(m_investors.insert(msg.sender, value));
emit LogNewInvestor(msg.sender, now, value);
}
if (m_paysys.mode == Paymode.Pull)
assert(m_investors.setPaymentTime(msg.sender, now));
emit LogNewInvesment(msg.sender, now, value);
investmentsNum++;
}
function payout() public notOnPause onlyAdmin(AccessRank.Payout) atPaymode(Paymode.Push) balanceChanged {
if (m_nextWave) {
nextWave();
return;
}
// if m_paysys.latestKeyIndex == m_investors.iterStart() then payout NOT in process and we must check latest time of payment.
if (m_paysys.latestKeyIndex == m_investors.iterStart()) {
require(now>m_paysys.latestTime+12 hours, "the latest payment was earlier than 12 hours");
m_paysys.latestTime = now;
}
uint i = m_paysys.latestKeyIndex;
uint value;
uint refBonus;
uint size = m_investors.size();
address investorAddr;
// gasleft and latest key index - prevent gas block limit
for (i; i < size && gasleft() > 50000; i++) {
investorAddr = m_investors.keyFromIndex(i);
(value, refBonus) = m_investors.investorShortInfo(investorAddr);
value = m_dividendsPercent.mul(value);
if (address(this).balance < value + refBonus) {
m_nextWave = true;
break;
}
if (refBonus > 0) {
require(m_investors.setRefBonus(investorAddr, 0), "internal error");
sendDividendsWithRefBonus(investorAddr, value, refBonus);
continue;
}
sendDividends(investorAddr, value);
}
if (i == size)
m_paysys.latestKeyIndex = m_investors.iterStart();
else
m_paysys.latestKeyIndex = i;
}
function setAdminAddr(address addr) public onlyAdmin(AccessRank.Full) {
addr.requireNotZero();
if (adminAddr != addr) {
adminAddr = addr;
emit LogAdminAddrChanged(addr, now);
}
}
function setPayerAddr(address addr) public onlyAdmin(AccessRank.Full) {
addr.requireNotZero();
if (payerAddr != addr) {
payerAddr = addr;
emit LogPayerAddrChanged(addr, now);
}
}
function setPullPaymode() public onlyAdmin(AccessRank.Paymode) atPaymode(Paymode.Push) {
changePaymode(Paymode.Pull);
}
function getMemInvestor(address addr) internal view returns(InvestorsStorage.investor) {
(uint a, uint b, uint c, uint d) = m_investors.investorFullInfo(addr);
return InvestorsStorage.investor(a, b, c, d);
}
function notZeroNotSender(address addr) internal view returns(bool) {
return addr.notZero() && addr != msg.sender;
}
function sendDividends(address addr, uint value) private {
if (addr.send(value)) emit LogPayDividends(addr, now, value);
}
function sendDividendsWithRefBonus(address addr, uint value, uint refBonus) private {
if (addr.send(value+refBonus)) {
emit LogPayDividends(addr, now, value);
emit LogPayReferrerBonus(addr, now, refBonus);
}
}
function nextWave() private {
m_investors = new InvestorsStorage();
changePaymode(Paymode.Push);
m_paysys.latestKeyIndex = m_investors.iterStart();
investmentsNum = 0;
waveStartup = now;
m_nextWave = false;
emit LogNextWave(now);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment