Skip to content

Instantly share code, notes, and snippets.

@ramilexe
Created February 25, 2020 12:15
Show Gist options
  • Save ramilexe/7d92eddd35deebc5d792566e25108fee to your computer and use it in GitHub Desktop.
Save ramilexe/7d92eddd35deebc5d792566e25108fee to your computer and use it in GitHub Desktop.
ConsiderItDone Solidity Smart Contract demo

EverId Smart Contracts

Details of spendings accounting

Current day spendings can be accessed by spentToday storage mapping call. A transaction is required to update the spendings data. So, if a new day has come, but transactions have not yet occurred, previous day data is stored. To update spendings data you need to initiate a transaction to reset the limits (for example, send submitTransaction transaction).

To get actual data of spendings, you need to call lastDay and check timestamp. If timestamp equals to today's 00:00 am, then the data is actual. Otherwise, there are no new transactions today, so you can consider today's spendings equal to 0.

pragma solidity ^0.4.21;
import './TransactionToken.sol';
contract CreditToken is TransactionToken {
string public name = "Credit Token";
string public symbol = "CRDT";
uint8 public decimals = 18;
}
pragma solidity ^0.4.21;
contract DateTime {
/*
* Date and Time utilities for ethereum contracts
*
*/
struct _DateTime {
uint16 year;
uint8 month;
uint8 day;
uint8 hour;
uint8 minute;
uint8 second;
uint8 weekday;
}
uint constant DAY_IN_SECONDS = 86400;
uint constant YEAR_IN_SECONDS = 31536000;
uint constant LEAP_YEAR_IN_SECONDS = 31622400;
uint constant HOUR_IN_SECONDS = 3600;
uint constant MINUTE_IN_SECONDS = 60;
uint16 constant ORIGIN_YEAR = 1970;
function isLeapYear(uint16 year) public pure returns (bool) {
if (year % 4 != 0) {
return false;
}
if (year % 100 != 0) {
return true;
}
if (year % 400 != 0) {
return false;
}
return true;
}
function leapYearsBefore(uint year) public pure returns (uint) {
year -= 1;
return year / 4 - year / 100 + year / 400;
}
function getDaysInMonth(uint8 month, uint16 year) public pure returns (uint8) {
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
return 31;
}
else if (month == 4 || month == 6 || month == 9 || month == 11) {
return 30;
}
else if (isLeapYear(year)) {
return 29;
}
else {
return 28;
}
}
function parseTimestamp(uint timestamp) internal pure returns (_DateTime dt) {
uint secondsAccountedFor = 0;
uint buf;
uint8 i;
// Year
dt.year = getYear(timestamp);
buf = leapYearsBefore(dt.year) - leapYearsBefore(ORIGIN_YEAR);
secondsAccountedFor += LEAP_YEAR_IN_SECONDS * buf;
secondsAccountedFor += YEAR_IN_SECONDS * (dt.year - ORIGIN_YEAR - buf);
// Month
uint secondsInMonth;
for (i = 1; i <= 12; i++) {
secondsInMonth = DAY_IN_SECONDS * getDaysInMonth(i, dt.year);
if (secondsInMonth + secondsAccountedFor > timestamp) {
dt.month = i;
break;
}
secondsAccountedFor += secondsInMonth;
}
// Day
for (i = 1; i <= getDaysInMonth(dt.month, dt.year); i++) {
if (DAY_IN_SECONDS + secondsAccountedFor > timestamp) {
dt.day = i;
break;
}
secondsAccountedFor += DAY_IN_SECONDS;
}
// Hour
dt.hour = getHour(timestamp);
// Minute
dt.minute = getMinute(timestamp);
// Second
dt.second = getSecond(timestamp);
// Day of week.
dt.weekday = getWeekday(timestamp);
}
function getYear(uint timestamp) public pure returns (uint16) {
uint secondsAccountedFor = 0;
uint16 year;
uint numLeapYears;
// Year
year = uint16(ORIGIN_YEAR + timestamp / YEAR_IN_SECONDS);
numLeapYears = leapYearsBefore(year) - leapYearsBefore(ORIGIN_YEAR);
secondsAccountedFor += LEAP_YEAR_IN_SECONDS * numLeapYears;
secondsAccountedFor += YEAR_IN_SECONDS * (year - ORIGIN_YEAR - numLeapYears);
while (secondsAccountedFor > timestamp) {
if (isLeapYear(uint16(year - 1))) {
secondsAccountedFor -= LEAP_YEAR_IN_SECONDS;
}
else {
secondsAccountedFor -= YEAR_IN_SECONDS;
}
year -= 1;
}
return year;
}
function getMonth(uint timestamp) public pure returns (uint8) {
return parseTimestamp(timestamp).month;
}
function getDay(uint timestamp) public pure returns (uint8) {
return parseTimestamp(timestamp).day;
}
function getHour(uint timestamp) public pure returns (uint8) {
return uint8((timestamp / 60 / 60) % 24);
}
function getMinute(uint timestamp) public pure returns (uint8) {
return uint8((timestamp / 60) % 60);
}
function getSecond(uint timestamp) public pure returns (uint8) {
return uint8(timestamp % 60);
}
function getWeekday(uint timestamp) public pure returns (uint8) {
return uint8((timestamp / DAY_IN_SECONDS + 4) % 7);
}
function toTimestamp(uint16 year, uint8 month, uint8 day) public pure returns (uint timestamp) {
return toTimestamp(year, month, day, 0, 0, 0);
}
function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour) public pure returns (uint timestamp) {
return toTimestamp(year, month, day, hour, 0, 0);
}
function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute) public pure returns (uint timestamp) {
return toTimestamp(year, month, day, hour, minute, 0);
}
function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute, uint8 second) public pure returns (uint timestamp) {
uint16 i;
// Year
for (i = ORIGIN_YEAR; i < year; i++) {
if (isLeapYear(i)) {
timestamp += LEAP_YEAR_IN_SECONDS;
}
else {
timestamp += YEAR_IN_SECONDS;
}
}
// Month
uint8[12] memory monthDayCounts;
monthDayCounts[0] = 31;
if (isLeapYear(year)) {
monthDayCounts[1] = 29;
}
else {
monthDayCounts[1] = 28;
}
monthDayCounts[2] = 31;
monthDayCounts[3] = 30;
monthDayCounts[4] = 31;
monthDayCounts[5] = 30;
monthDayCounts[6] = 31;
monthDayCounts[7] = 31;
monthDayCounts[8] = 30;
monthDayCounts[9] = 31;
monthDayCounts[10] = 30;
monthDayCounts[11] = 31;
for (i = 1; i < month; i++) {
timestamp += DAY_IN_SECONDS * monthDayCounts[i - 1];
}
// Day
timestamp += DAY_IN_SECONDS * (day - 1);
// Hour
timestamp += HOUR_IN_SECONDS * (hour);
// Minute
timestamp += MINUTE_IN_SECONDS * (minute);
// Second
timestamp += second;
return timestamp;
}
}
pragma solidity ^0.4.21;
import './TransactionToken.sol';
contract IDToken is TransactionToken {
string public name = "ID Token";
string public symbol = "ID";
uint8 public decimals = 18;
}
pragma solidity ^0.4.21;
import './IDToken.sol';
import './CreditToken.sol';
import './DateTime.sol';
contract OrganizationMultiSigWallet {
/*
* Events
*/
event Confirmation(address indexed sender, uint indexed transactionId);
event Revocation(address indexed sender, uint indexed transactionId);
event Submission(uint indexed transactionId);
event Execution(uint indexed transactionId);
event ExecutionFailure(uint indexed transactionId);
event Deposit(address indexed sender, uint value);
event AgentAddition(address indexed agent);
event AdminAddition(address indexed admin);
event AgentRemoval(address indexed agent);
event AdminRemoval(address indexed admin);
event RequirementChange(uint required);
/*
* Storage
*/
mapping(uint => Transaction) public transactions;
uint public transactionsCount;
mapping(uint => mapping(address => bool)) public confirmations;
mapping(address => bool) public isAgent; // wallet owner that is agent
mapping(address => bool) public isAdmin; // wallet owner that is admin
mapping(address => Spendings) public spentToday;
address[] public admins;
address[] public agents;
string public name; // organization name
address public IDTokenAddress;
address public CreditTokenAddress;
address public DateTimeAddress;
address public rootOwner;
uint public required; // number of confirmations
uint public dailyLimitEth;
uint public dailyLimitIDToken;
uint public dailyLimitCreditToken;
uint public lastDay; //timestamp
enum ValueTypes {Eth, ID, Credit}
struct Transaction {
address sender;
address destination;
uint value;
ValueTypes valueType;
uint timestamp;
bool executed;
}
struct Spendings {
uint spentEth;
uint spentIDToken;
uint spentCreditToken;
}
/*
* Modifiers
*/
modifier onlyWallet() {
require(msg.sender == address(this));
_;
}
modifier onlyRootOwner() {
require(msg.sender == rootOwner);
_;
}
modifier onlyRootOwnerOrAdmin() {
require(msg.sender == rootOwner || isAdmin[msg.sender]);
_;
}
modifier onlyWalletOwner() {
require(isWalletOwner(msg.sender));
_;
}
modifier onlyAgent() {
require(isAgent[msg.sender]);
_;
}
modifier onlyAdmin() {
require(isAdmin[msg.sender]);
_;
}
modifier adminDoesNotExist(address admin) {
require(!isAdmin[admin]);
_;
}
modifier adminExists(address admin) {
require(isAdmin[admin]);
_;
}
modifier agentDoesNotExist(address agent) {
require(!isAgent[agent]);
_;
}
modifier agentExists(address agent) {
require(isAgent[agent]);
_;
}
modifier ownerDoesNotExist(address owner) {
require(!isWalletOwner(owner));
_;
}
modifier ownerExists(address owner) {
require(isWalletOwner(owner));
_;
}
modifier transactionExists(uint transactionId) {
require(transactions[transactionId].destination != 0);
_;
}
modifier confirmed(uint transactionId, address owner) {
require(confirmations[transactionId][owner]);
_;
}
modifier notConfirmed(uint transactionId, address owner) {
require(!confirmations[transactionId][owner]);
_;
}
modifier notExecuted(uint transactionId) {
require(!transactions[transactionId].executed);
_;
}
modifier notNull(address _address) {
require(_address != 0);
_;
}
modifier validRequirement(uint ownerCount, uint _required) {
require(_required <= ownerCount
&& _required != 0
&& ownerCount != 0);
_;
}
modifier limitNotReached(address sender, uint value, ValueTypes valueType) {
updateDailyLimits();
// check limits
if (isAgent[sender]) {
if (valueType == ValueTypes.Eth) require((spentToday[sender].spentEth + value) <= dailyLimitEth);
if (valueType == ValueTypes.ID) require((spentToday[sender].spentIDToken + value) <= dailyLimitIDToken);
if (valueType == ValueTypes.Credit) require((spentToday[sender].spentCreditToken + value) <= dailyLimitCreditToken);
}
_;
}
/// @dev Fallback function allows to deposit ether.
function() public payable
{
if (msg.value > 0)
emit Deposit(msg.sender, msg.value);
}
/*
* Public functions
*/
/// @dev Set limits to zero for agents if a new day has come
function updateDailyLimits() public {
if (now > (lastDay + 24 hours)) {
// set today 00:00 as current day
lastDay = DateTime(DateTimeAddress).toTimestamp(DateTime(DateTimeAddress).getYear(now), DateTime(DateTimeAddress).getMonth(now), DateTime(DateTimeAddress).getDay(now));
// update all agents
for (uint i = 0; i < agents.length; i++) {
spentToday[agents[i]] = Spendings({
spentEth : 0,
spentIDToken : 0,
spentCreditToken : 0
});
}
}
}
function isWalletOwner(address owner)
public
view
returns (bool)
{
return (owner == rootOwner || isAdmin[owner] || isAgent[owner]);
}
function getWalletOwnersCount()
public
view
returns (uint)
{
return uint(admins.length + agents.length + 1);
}
constructor(string _name, uint _dailyLimitEth, uint _dailyLimitIDToken, uint _dailyLimitCreditToken, address _IDTokenAddress, address _CreditTokenAddress, address _DateTimeAddress)
public
{
//admins.push(msg.sender);
required = uint(1);
rootOwner = msg.sender;
name = _name;
dailyLimitEth = _dailyLimitEth;
dailyLimitIDToken = _dailyLimitIDToken;
dailyLimitCreditToken = _dailyLimitCreditToken;
IDTokenAddress = _IDTokenAddress;
CreditTokenAddress = _CreditTokenAddress;
DateTimeAddress = _DateTimeAddress;
}
function setDailyLimit(uint value, ValueTypes valueType)
public
onlyRootOwner
{
if (valueType == ValueTypes.Eth) dailyLimitEth = value;
if (valueType == ValueTypes.ID) dailyLimitIDToken = value;
if (valueType == ValueTypes.Credit) dailyLimitCreditToken = value;
}
function addAdmin(address admin)
public
onlyRootOwner
adminDoesNotExist(admin)
notNull(admin)
{
isAdmin[admin] = true;
admins.push(admin);
emit AdminAddition(admin);
}
function addAgent(address agent)
public
onlyRootOwnerOrAdmin
agentDoesNotExist(agent)
notNull(agent)
{
isAgent[agent] = true;
agents.push(agent);
emit AgentAddition(agent);
}
function removeAdmin(address admin)
public
onlyRootOwner
adminExists(admin)
{
isAdmin[admin] = false;
for (uint i = 0; i < admins.length; i++)
if (admins[i] == admin) {
admins[i] = admins[admins.length - 1];
break;
}
if (admins.length > 0) admins.length -= 1;
if (required > getWalletOwnersCount())
changeRequirement(getWalletOwnersCount());
emit AdminRemoval(admin);
}
function removeAgent(address agent)
public
onlyRootOwnerOrAdmin
agentExists(agent)
{
isAgent[agent] = false;
for (uint i = 0; i < agents.length; i++)
if (agents[i] == agent) {
agents[i] = agents[agents.length - 1];
break;
}
if (agents.length > 0) agents.length -= 1;
if (required > getWalletOwnersCount())
changeRequirement(getWalletOwnersCount());
emit AgentRemoval(agent);
}
/// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
/// @param _required Number of required confirmations.
function changeRequirement(uint _required)
public
onlyWallet
validRequirement(getWalletOwnersCount(), _required)
{
required = _required;
emit RequirementChange(_required);
}
/// @dev Allows an owner to add and confirm (send) a transaction.
/// @param destination Transaction target address.
/// @param value Transaction value.
/// @param valueType Transaction value type (Eth, ID, Credit).
/// @return Returns transaction ID.
function submitTransaction(address destination, uint value, ValueTypes valueType)
public
onlyWalletOwner
limitNotReached(msg.sender, value, valueType)
returns (uint transactionId)
{
transactionId = addTransaction(msg.sender, destination, value, valueType);
//confirmTransaction(transactionId);// who can confirm. admins?
executeTransactionWithoutConfirmations(transactionId);
// add used limits
if (isAgent[msg.sender]) {
if (valueType == ValueTypes.Eth) spentToday[msg.sender].spentEth += value;
if (valueType == ValueTypes.ID) spentToday[msg.sender].spentIDToken += value;
if (valueType == ValueTypes.Credit) spentToday[msg.sender].spentCreditToken += value;
}
}
function executeTransactionWithoutConfirmations(uint transactionId)
internal
ownerExists(msg.sender)
transactionExists(transactionId)
notExecuted(transactionId)
{
Transaction storage txn = transactions[transactionId];
txn.executed = true;
txn.timestamp = now;
if (txn.valueType == ValueTypes.Eth) {
//if (external_call(txn.destination, txn.value, 0, ""))
txn.destination.transfer(txn.value);
emit Execution(transactionId);
} else {//tokens
if (txn.valueType == ValueTypes.ID) {
IDToken(IDTokenAddress).transfer(txn.destination, txn.value);
emit Execution(transactionId);
} else if (txn.valueType == ValueTypes.Credit) {
CreditToken(CreditTokenAddress).transfer(txn.destination, txn.value);
emit Execution(transactionId);
} else {
txn.executed = true;
emit ExecutionFailure(transactionId);
}
}
}
/*
/// @dev Allows an owner to confirm a transaction.
/// @param transactionId Transaction ID.
function confirmTransaction(uint transactionId)
public
ownerExists(msg.sender)
transactionExists(transactionId)
notConfirmed(transactionId, msg.sender)
{
confirmations[transactionId][msg.sender] = true;
emit Confirmation(msg.sender, transactionId);
executeTransaction(transactionId);
}
/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param transactionId Transaction ID.
function revokeConfirmation(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
confirmations[transactionId][msg.sender] = false;
emit Revocation(msg.sender, transactionId);
}
/// @dev Allows anyone to execute a confirmed transaction.
/// @param transactionId Transaction ID.
function executeTransaction(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
if (isConfirmed(transactionId)) {
Transaction storage txn = transactions[transactionId];
txn.executed = true;
if (external_call(txn.destination, txn.value, txn.data.length, txn.data))
emit Execution(transactionId);
else {
emit ExecutionFailure(transactionId);
txn.executed = false;
}
}
}
*
// call has been separated into its own function in order to take advantage
// of the Solidity's code generator to produce a loop that copies tx.data into memory.
function external_call(address destination, uint value, uint dataLength, bytes data) private returns (bool) {
bool result;
assembly {
let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that
result := call(
sub(gas, 34710), // 34710 is the value that solidity is currently emitting
// It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
// callNewAccountGas (25000, in case the destination address does not exist and needs creating)
destination,
value,
d,
dataLength, // Size of the input (in bytes) - this is what fixes the padding problem
x,
0 // Output is ignored, therefore the output size is zero
)
}
return result;
}
/*
/// @dev Returns the confirmation status of a transaction.
/// @param transactionId Transaction ID.
/// @return Confirmation status.
function isConfirmed(uint transactionId)
public
constant
returns (bool)
{
uint count = 0;
for (uint i = 0; i < walletOwners.length; i++) { // who can confirm. admins?
if (confirmations[transactionId][walletOwners[i]])
count += 1;
if (count == required)
return true;
}
}
*/
/*
* Internal functions
*/
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param _destination Transaction target address.
/// @param _value Transaction value.
/// @param _type Transaction value type.
/// @return Returns transaction ID.
function addTransaction(address _sender, address _destination, uint _value, ValueTypes _type)
internal
notNull(_destination)
returns (uint transactionId)
{
transactionId = transactionsCount;
transactions[transactionId] = Transaction({
sender : _sender,
destination : _destination,
value : _value,
valueType : _type,
timestamp: now,
executed : false
});
transactionsCount += 1;
emit Submission(transactionId);
}
/*
* Web3 call functions
*
/// @dev Returns number of confirmations of a transaction.
/// @param transactionId Transaction ID.
/// @return Number of confirmations.
function getConfirmationCount(uint transactionId)
public
constant
returns (uint count)
{
for (uint i = 0; i < walletOwners.length; i++)
if (confirmations[transactionId][walletOwners[i]])
count += 1;
}
*/
/// @dev Returns total number of transactions after filers are applied.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return Total number of transactions after filters are applied.
function getTransactionsCount(bool pending, bool executed)
public
view
returns (uint count)
{
for (uint i = 0; i < transactionsCount; i++)
if (pending && !transactions[i].executed
|| executed && transactions[i].executed)
count += 1;
}
function getAdmins()
public
view
returns (address[])
{
return admins;
}
function getAgents()
public
view
returns (address[])
{
return agents;
}
/*
/// @dev Returns array with owner addresses, which confirmed transaction.
/// @param transactionId Transaction ID.
/// @return Returns array of owner addresses.
function getConfirmations(uint transactionId)
public
constant
returns (address[] _confirmations)
{
address[] memory confirmationsTemp = new address[](walletOwners.length);
uint count = 0;
uint i;
for (i = 0; i < walletOwners.length; i++)
if (confirmations[transactionId][walletOwners[i]]) {
confirmationsTemp[count] = walletOwners[i];
count += 1;
}
_confirmations = new address[](count);
for (i = 0; i < count; i++)
_confirmations[i] = confirmationsTemp[i];
}
*/
/// @dev Returns list of transaction IDs in defined range.
/// @param from Index start position of transaction array.
/// @param to Index end position of transaction array.
/// @param pending Include pending transactions.
/// @param executed Include executed transactions.
/// @return Returns array of transaction IDs.
function getTransactionIds(uint from, uint to, bool pending, bool executed)
public
view
returns (uint[] _transactionIds)
{
uint[] memory transactionIdsTemp = new uint[](transactionsCount);
uint count = 0;
uint i;
for (i = 0; i < transactionsCount; i++)
if (pending && !transactions[i].executed
|| executed && transactions[i].executed)
{
transactionIdsTemp[count] = i;
count += 1;
}
_transactionIds = new uint[](to - from);
for (i = from; i < to; i++)
_transactionIds[i - from] = transactionIdsTemp[i];
}
}
pragma solidity ^0.4.21;
import '../node_modules/openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol';
contract TransactionToken is MintableToken {
/*
* Events
*/
event Submission(uint indexed transactionId);
/*
* Storage
*/
mapping(uint => Transaction) public transactions;
uint public transactionsCount;
struct Transaction {
address sender;
address destination;
uint value;
uint timestamp;
}
/*
* Modifiers
*/
modifier notNull(address _address) {
require(_address != 0);
_;
}
/*
* Internal functions
*/
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param _destination Transaction target address.
/// @param _value Transaction value.
/// @return Returns transaction ID.
function addTransaction(address _sender, address _destination, uint _value)
internal
notNull(_destination)
returns (uint transactionId)
{
transactionId = transactionsCount;
transactions[transactionId] = Transaction({
sender : _sender,
destination : _destination,
value : _value,
timestamp : now
});
transactionsCount += 1;
emit Submission(transactionId);
}
/*
* Overrides
*/
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
addTransaction(_from, _to, _value);
return true;
}
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
addTransaction(msg.sender, _to, _value);
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment