Skip to content

Instantly share code, notes, and snippets.

@chriseth
Created July 16, 2020 13:34
Show Gist options
  • Save chriseth/7f087fc42c4c2d115bc04b8fe6948d69 to your computer and use it in GitHub Desktop.
Save chriseth/7f087fc42c4c2d115bc04b8fe6948d69 to your computer and use it in GitHub Desktop.

Revisions

  1. chriseth created this gist Jul 16, 2020.
    114 changes: 114 additions & 0 deletions Token.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,114 @@
    // unmodified

    contract Token {

    uint8 public decimals = 18;

    string public name;
    uint256 public lastTouched;
    address public hub;
    address public owner;
    uint256 public inflationOffset;
    uint256 public currentIssuance;

    modifier onlyHub() {
    require(msg.sender == hub);
    _;
    }

    modifier onlyOwner() {
    require(msg.sender == owner);
    _;
    }

    constructor(address _owner, string memory _name, uint256 initialPayout) public {
    require(_owner != address(0));
    name = _name;
    owner = _owner;
    hub = msg.sender;
    lastTouched = time();
    inflationOffset = findInflationOffset();
    currentIssuance = HubI(hub).issuance();
    _mint(_owner, initialPayout);
    }

    function time() public view returns (uint) {
    return block.timestamp;
    }

    function symbol() public view returns (string memory) {
    return HubI(hub).symbol();
    }

    function inflation() public view returns (uint256) {
    return HubI(hub).inflation();
    }

    function divisor() public view returns (uint256) {
    return HubI(hub).divisor();
    }

    function period() public view returns (uint256) {
    return HubI(hub).period();
    }

    function periods() public view returns (uint256) {
    return HubI(hub).periods();
    }

    function periodsLastTouched() public view returns (uint256) {
    return (lastTouched.sub(hubDeploy())).div(period());
    }

    function hubDeploy() public view returns (uint256) {
    return HubI(hub).deployedAt();
    }

    function findInflationOffset() public view returns (uint256) {
    return ((period().mul(periods().add(1))).add(hubDeploy())).sub(time());
    }

    function look() public view returns (uint256) {
    uint256 payout = 0;
    uint256 clock = lastTouched;
    uint256 offset = inflationOffset;
    uint256 rate = currentIssuance;
    uint256 p = periodsLastTouched();
    while (clock.add(offset) <= time()) {
    payout = payout.add(offset.mul(rate));
    clock = clock.add(offset);
    offset = period();
    p = p.add(1);
    rate = HubI(hub).issuanceStep(p);
    }
    uint256 timePassed = time().sub(clock);
    payout = payout.add(timePassed.mul(rate));
    return payout;
    }

    function update() public returns (uint256) {
    uint256 gift = look();
    if (gift > 0) {
    inflationOffset = findInflationOffset();
    lastTouched = time();
    currentIssuance = HubI(hub).issuance();
    _mint(owner, gift);
    }
    }

    function hubTransfer(
    address from, address to, uint256 amount
    ) public onlyHub returns (bool) {
    _transfer(from, to, amount);
    }

    function transfer(address dst, uint wad) public returns (bool) {
    // this totally redundant code is covering what I believe is weird compiler
    // eccentricity, making gnosis's revert message not correctly return the gas
    // when this function only super() calls the inherited contract
    if (msg.sender == owner) {
    owner = msg.sender;
    }
    return super.transfer(dst, wad);
    }
    }
    272 changes: 272 additions & 0 deletions hub.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,272 @@
    pragma solidity ^0.5.0;

    import "./Token.sol";
    import "./util.sol";

    import { ResizableArray } from "./resizableArray.sol";


    export {Hub}

    struct Flow
    {
    address identity;
    uint256 sent;
    uint256 received;
    }

    struct Transfer
    {
    address tokenOwner;
    address src;
    address dst;
    uint amount;
    }

    error PathTooComplex();
    error PathInvalid(string reason);
    error TrustLimitExceeded(Transfer transfer, uint maxAmount);



    function appendIfNotExists(ResizableArray[Flow] memory flows, address user) returns (Flow memory)
    {
    for (uint i = 0; i < flows.length; i++)
    if (flows.at(i).identity == user)
    return flows.at(i);
    flows.push(Flow{user, 0, 0});
    return flows.at(flows.length - 1);
    }

    function integrateTransfer(ResizableArray[Flow] memory flows, Transfer calldata transfer)
    {
    appendIfNotExists(flows, transfer.src).sent += transfer.amount;
    appendIfNotExists(flows, transfer.dst).received += transfer.amount;
    }

    function validateTransferThrough(ResizableArray[Flow] memory flows) internal {
    // a valid path has only one true sender and reciever, for all other
    // addresses in the path, sent = received
    // also, the sender should be msg.sender
    address src;
    address dest;
    for (uint i = 0; i < flows.length; i++)
    {
    Flow memory f = flows.at(i);
    if (f.sent > f.received)
    {
    require(src == address(0), PathInvalid("Path sends from more than one src"));
    require(f.identity == msg.sender, PathInvalid("Path doesn't send from transaction sender"));
    require(f.received == 0, PathInvalid("Sender is receiving"));
    src = f.identity;
    }
    if (f.received > f.sent)
    {
    require(dest == address(0), PathInvalid("Path sends to more than one dest"));
    require(f.sent == 0, PathInvalid("Receiver is sending"));
    dest = f.identity;
    }
    }
    require(src != address(0), PathInvalid("Transaction must have a src"));
    require(dest != address(0), PathInvalid("Transaction must have a dest"));
    // the total amounts sent and received by src and dest should match
    require(
    appendIfNotExists(flows, src).sent ==
    appendIfNotExists(flows, dest).received,
    "Unequal sent and received amounts"
    );
    emit HubTransfer(src, dest, validation[src].sent);
    }




    contract Hub {
    address public owner;

    uint256 public inflation;
    uint256 public divisor;
    uint256 public period;
    string public symbol; // = 'CRC';
    uint256 public initialPayout;
    uint256 public initialIssuance;
    uint256 public deployedAt;

    mapping (address => Token) public userToToken;
    mapping (address => address) public tokenToUser;
    mapping (address => mapping (address => uint256)) public limits;

    event Signup(address indexed user, address token);
    event Trust(address indexed canSendTo, address indexed user, uint256 limit);
    event HubTransfer(address indexed from, address indexed to, uint256 amount);


    mapping (address => transferValidator) public validation;
    address[] public seen;

    modifier onlyOwner() {
    require (msg.sender == owner);
    _;
    }

    constructor(address _owner, uint256 _inflation, uint256 _period, string memory _symbol, uint256 _initialPayout, uint256 _initialIssuance) public {
    require (_owner != address(0));
    owner = _owner;
    inflation = _inflation;
    divisor = findDivisor(_inflation);
    period = _period;
    symbol = _symbol;
    initialPayout = _initialPayout;
    initialIssuance = _initialIssuance;
    deployedAt = block.timestamp;
    }

    function findDivisor(uint256 _inf) internal pure returns (uint256) {
    uint256 iter = 0;
    while (_inf.div(pow(10, iter)) > 9) {
    iter += 1;
    }
    return pow(10, iter);
    }

    function periods() public view returns (uint256) {
    return (block.timestamp.sub(deployedAt)).div(period);
    }

    function issuance() public view returns (uint256) {
    return inflate(initialIssuance, periods());
    }

    function issuanceStep(uint256 _periods) public view returns (uint256) {
    return inflate(initialIssuance, _periods);
    }

    function inflate(uint256 _initial, uint256 _periods) public view returns (uint256) {
    uint256 q = pow(inflation, _periods);
    uint256 d = pow(divisor, _periods);
    return (_initial.mul(q)).div(d);
    }

    function changeOwner(address _newOwner) public onlyOwner returns (bool) {
    require(_newOwner != address(0));
    owner = _newOwner;
    return true;
    }

    function updateInflation(uint256 _inflation) public onlyOwner returns (bool) {
    inflation = _inflation;
    return true;
    }

    function updateRate(uint256 _initialIssuance) public onlyOwner returns (bool) {
    initialIssuance = _initialIssuance;
    return true;
    }

    function updateSymbol(string memory _symbol) public onlyOwner returns (bool) {
    symbol = _symbol;
    return true;
    }

    function time() public view returns (uint256) { return block.timestamp; }

    // No exit allowed. Once you create a personal token, you're in for good.
    function signup(string memory _name) public returns (bool) {
    require(address(userToToken[msg.sender]) == address(0));

    Token token = new Token(msg.sender, _name, initialPayout);
    userToToken[msg.sender] = token;
    tokenToUser[address(token)] = msg.sender;
    _trust(msg.sender, 100);

    emit Signup(msg.sender, address(token));
    return true;
    }

    // Trust does not have to be reciprocated.
    // (e.g. I can trust you but you don't have to trust me)
    function trust(address user, uint limit) public {
    require(address(userToToken[msg.sender]) != address(0), "You can only trust people after you've signed up!");
    require(msg.sender != user, "You can't untrust yourself");
    _trust(user, limit);
    }

    function _trust(address user, uint limit) internal {
    limits[msg.sender][user] = limit;
    emit Trust(msg.sender, user, limit);
    }

    function pow(uint256 base, uint256 exponent) public pure returns (uint256) {
    if (base == 0) {
    return 0;
    }
    if (exponent == 0) {
    return 1;
    }
    if (exponent == 1) {
    return base;
    }
    uint256 y = 1;
    while(exponent > 1) {
    if(exponent.mod(2) == 0) {
    base = base.mul(base);
    exponent = exponent.div(2);
    } else {
    y = base.mul(y);
    base = base.mul(base);
    exponent = (exponent.sub(1)).div(2);
    }
    }
    return base.mul(y);
    }

    function checkSendLimit(address tokenOwner, address src, address dest) public view returns (uint256) {
    // there is no trust
    if (limits[dest][tokenOwner] == 0) {
    return 0;
    }
    // if dest hasn't signed up, they cannot trust anyone
    if (address(userToToken[dest]) == address(0)) {
    return 0;
    }

    // if the token doesn't exist, return max
    uint256 max = (userToToken[dest].totalSupply().mul(limits[dest][tokenOwner])).div(100);
    if (address(userToToken[tokenOwner]) == address(0)) {
    return max;
    }

    // if sending dest's token to dest, src can send 100% of their holdings
    uint256 srcBalance = userToToken[tokenOwner].balanceOf(src);
    if (tokenOwner == dest) {
    return srcBalance;
    }
    uint256 destBalance = userToToken[tokenOwner].balanceOf(dest);

    // if trustLimit has already been overriden by a direct transfer, nothing more can be sent
    if (max < destBalance) return 0;
    return max.sub(destBalance);
    }

    function transferThrough(Transfer[] calldata transfers) public mut {
    require(transfers.length <= 10, PathTooComplex());

    ResizableArray[Flow] memory flows;
    // Template paramater values are auto-deduced here.
    // we still provide the [] to indicate it is a template function.
    fold[](transfers, flows, function(ResizableArray[Flow] memory flows, Transfer calldata transfer) view returns (ResizableArray[Flow] memory) {
    if (transfer.tokenOwner != transfer.dst) {
    uint256 max = checkSendLimit(transfer.tokenOwner, transfer.src, transfer.dst);
    require(
    userToToken[transfer.tokenOwner].balanceOf(dest) + transfer.amount <= max,
    TrustLimitExceeded(transfer, max)
    );
    }
    integrateTransfer(flows, transfer);
    userToToken[transfer.tokenOwner].hubTransfer(transfer.src, transfer.dest, transfer.amount);
    return flows;
    });
    validateTransferThrough(flows);
    }
    }

    83 changes: 83 additions & 0 deletions resizableArray.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    import { min } from "./util.sol";

    export { ResizableArray, IndexOutOfBounds };

    error IndexOutOfBounds();
    error InvalidReserve();

    // All declarations inside a template automatically turn into templates.
    // When accessed from outside, the value for T has to be explicitly
    // given (unless it is auto-deduced).
    // Inside the template area, template parameters can be mentioned
    // when accessing other templatized declartions but they can
    // also be omitted.
    template[T]
    {

    struct ResizableArray
    {
    uint length;
    T[] data;

    invariant { length < data.length }
    }

    // "member" attaches the function as a member to the type.
    // alternative: "bound"?
    // It cannot be called without the type.
    // Problem: the inner workings may be obivous if the first parameter is a struct,
    // but it may not be that obvious if it is a complex array.
    // Name clashes could maybe only be detected at the point where the function is used.


    /// Reserves a certain capacity but does not change the length of
    /// the array. Re-allocates if needed.
    /// capacity has to be at least the current length.
    member function reserve(ResizableArray memory array, uint capacity)
    {
    require(capacity >= array.length, InvalidReserve());
    if (capacity <= array.data.length)
    return;

    T[] memory newData = new T[](capacity);
    for (uint i = 0; i < array.length; i++)
    newData[i] = array.data[i];
    array.data = newData;
    }


    /// Sets the length to the given value and re-allocates the
    /// data if required.
    member function resize(ResizableArray memory array, uint length)
    {
    // grow if needed
    uint newCapacity = array.data.length;
    while (newCapacity < length) newCapacity *= 2;
    array.reserve(newCapacity);

    // zero-initialize in case we reduced the size
    for (uint i = array.length; i < length; i++)
    {
    // for template types, we allow data location even if the template
    // parameter is a value type
    T memory zero;
    array.data[i] = zero;
    }
    array.length = length;
    assert(array.length <= array.data.length);
    }

    member function at(ResizableArray memory array, uint index) returns (T memory)
    {
    require(index < array.length, IndexOutOfBounds());
    return array.data[index];
    }


    member function push(ResizableArray memory array, T memory value)
    {
    array.resize(array.length + 1);
    array.at(array.length - 1) = value;
    }

    }
    27 changes: 27 additions & 0 deletions utils.sol
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,27 @@

    export {min, max, fold}

    function min(uint a, uint b) returns (uint)
    {
    return a < b ? a : b;
    }

    function max(uint a, uint b) returns (uint)
    {
    return a > b ? a : b;
    }


    // Problems here:
    // - we need another definition for calldata or storage arrays. We could also say that the
    // data location needs to be part of the templat etype, but then we cannot just
    // replace `T`, since memory has to go after the `[]`.
    // - the state mutability of the function type cannot be specified.
    function fold[T, V, F](
    T[] memory _array, V memory initial,
    function(V memory, T memory) returns (V memory) f
    ) returns (V memory result) {
    result = initial;
    for (uint i = 0; i < _array.length; i++)
    result = f(result, _array[i]);
    }