Mistake while building an EIP20-compatible token with rational numbers

qqwyqqwy Member Posts: 15
As a proof-of-concept to learn Solidity better, I tried writing an EIP20-compatible token that internally does not store the balances as a uint256, but as a rational number (with both the numerator and the denominator being a uint256).

However, there is a mistake somewhere in the code, because the `transfer` command gobbles up all available gas.
I haven't been able to find it. Help is greatly appreciated!

pragma solidity ^0.4.11;
// Based on https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol

/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}

function div(uint256 a, uint256 b) internal returns (uint256) {
// assert(b > 0); // Solidity automatically throws 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 returns (uint256) {
assert(b <= a);
return a - b;
}

function add(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}


// math/Rational.sol
pragma solidity ^0.4.11;
/*
Author: Qqwy/Wiebe-Marten Wijnja
Date: 2017-07-11
*/
import './SafeMath.sol';

/*
A simple library,
to allow working with the behaviour of and possibilities with
rational numbers on the Ethereum blockchain.
*/
library Rational{

struct RationalNumber {
uint256 num;
uint256 denom;
}
/* Creates a new number and simplifies it as much as possible */
function num(uint256 num, uint256 denom) internal constant returns (RationalNumber res) {
return simplify(RationalNumber(num, denom));
}

/* Returns the greatest common divisor of two uints. */
function gcd(uint256 a, uint256 b) constant returns (uint256) {
if (a == 0) return b;
if (b == 0) return a;

return gcd(b, a % b);
}

/* Returns a simplified rational number. */
function simplify(RationalNumber a) internal returns (RationalNumber res) {
require (a.denom != 0); // Prevent rational numbers with improper denominator ('division by 0')

uint256 gcdiv = gcd(a.num, a.denom);
uint256 new_denom = a.denom / gcdiv;
return RationalNumber(a.num / gcdiv, new_denom);
}

/* -1 if a < b, 0 if the same, 1 if b > a.*/
function compare(RationalNumber a, RationalNumber b) internal returns (int8) {
uint256 normalized_a = SafeMath.mul(a.num, b.denom);
uint256 normalized_b = SafeMath.mul(b.num, a.denom);
if (normalized_a == normalized_b) return 0;
if (normalized_a < normalized_b) return -1;
return 1;
}

/* Adds two Rational Numbers. Raises on overflow/underflow */
function add(RationalNumber a, RationalNumber b) internal returns (RationalNumber res) {
uint256 new_num = SafeMath.add(SafeMath.mul(a.num, b.denom), SafeMath.mul(b.num, a.denom));
uint256 new_denom = SafeMath.mul(a.denom, b.denom);
return simplify(RationalNumber(new_num, new_denom));
}

/* Subtracts two Rational Numbers. Raises on overflow/underflow */
function sub(RationalNumber a, RationalNumber b) internal returns (RationalNumber res) {
uint256 new_num = SafeMath.sub(SafeMath.mul(a.num, b.denom), SafeMath.mul(b.num, a.denom));
uint256 new_denom = SafeMath.mul(a.denom, b.denom);
return simplify(RationalNumber(uint256(new_num), new_denom));
}

/* This result might be truncated... */
function toUint(RationalNumber number) internal returns (uint256 res) {
return number.num / number.denom;
}
}


pragma solidity ^0.4.11;
/*
Author: Qqwy/Wiebe-Marten Wijnja
Date: 2017-07-11

*/

import "./math/Rational.sol";

contract RationalToken {
//Part of EIP20
string public standard = "RationalToken v0.1.0";
string public name = "RationalToken";
string public symbol = "//";

Rational.RationalNumber originalSupply = Rational.num(1e9, 1);//Rational.RationalNumber(1e77, 1e68); // million(1e9) tokens, but with 68 decimal places.
uint256 public decimals = 68; // <- Part of EIP20

mapping (address => Rational.RationalNumber) balanceOfRationals;

event Transfer(address indexed from, address indexed to, uint256 value);

function RationalToken() {
balanceOfRationals[msg.sender] = Rational.simplify(originalSupply);
}

// TODO once things can get burned!
function totalSupply() external constant returns (uint256 totalSupply) {
return Rational.toUint(originalSupply);
}

// Part of EIP20
function balanceOf(address to_) external constant returns (uint256) {
var rational = balanceOfRationals[to_];
return Rational.toUint(rational);
}

function balanceOfRational(address to_) external constant returns (uint256, uint256) {
var rational = balanceOfRationals[to_];
return (rational.num, rational.denom);
}

// Part of EIP20
function transfer(address to_, uint256 value_) external returns (bool success) {
do_transferRational(to_, Rational.num(value_, 1));
return true;
}

function transferRational(address to_, uint256 value_num_, uint256 value_denom_) external returns (bool success) {
do_transferRational(to_, Rational.num(value_num_, value_denom_));
return true;
}

function do_transferRational(address to_, Rational.RationalNumber value_) internal {
/* require (to_ != 0x0); */
/* require (Rational.compare(balanceOfRationals[msg.sender], value_) != -1); // Cannot transfer more than stored funds. */

balanceOfRationals[msg.sender] = Rational.sub(balanceOfRationals[msg.sender], value_);
balanceOfRationals[to_] = Rational.add(balanceOfRationals[to_], value_);

Transfer(msg.sender, to_, Rational.toUint(value_));
// TODO TransferRational event.
}
}

Comments

  • o0ragman0oo0ragman0o Member, Moderator Posts: 1,252 mod
    Interesting project!

    Without back tracing too much,
     balanceOfRationals[to_] = Rational.add(balanceOfRationals[to_], value_);
    ends up passing a 0 value denom to:
    function simplify(RationalNumber a) internal returns (RationalNumber res) {
    require (a.denom != 0); // Prevent rational numbers with improper denominator ('division by 0')
Sign In or Register to comment.