My coding Journey: From 0 to mainNet

After seeing Jshanks21's post and reading some articles recommending to keep a journal I'm here. The main idea is to use this post as a diary of progress, doubts and ideas. Any suggestion or collaboration will be more than welcome!
To understand my motivation, I introduce myself. My name is Julian, I'm an industrial engineer from Argentina and I'm interested in the crypto world since about 2011 when with a friend we wanted to mine BTC but for lack of money we could not. I started to follow news and have a first approach to solidity in 2019 but I never had continuity.
I have been traveling in Mexico for 6 months and after seeing the way people live I wanted to start an organization with social - environmental purposes. This combined with the DAO revolution has resulted in me being here today.
You can find a prototype website with the fundamental ideas at https://omens.space and there you will also find the communication channels. But enough spam and let's code!

2 Likes

Day 0: I started with javascript in 2019, thanks to COVID. The approach was reading posts, documentation and tutorials on youtube. With solidity it was similar, but the great CryptoZombies appeared. For a while I was without using the computer but 1 month ago I came back to the game. This time I got to know the great amount of information that exists in OpenZeppelin/openzeppelin-contracts, its community (thank you very much for what you do!!) and Ethernaut, that until yesterday (with a little - quite a lot of help), I managed to advance to level 6.

1 Like

Day 1: The main focus of this post will be the development of the main token, organization's treasury contract, ICO contract, the analysis of making a token for governance and the million parameters and issues to be defined as we go along.

Reading:

DOUBTS:
. treasuryPercentage in my mind should go inside the constructor, but I've got " DeclarationError: Undeclared identifier. " if I put it there, so I think that it goes outside.
. I'm overriding _transfer from the openzeppeling ERC20 token. I'm introducing a potential flaw to attack?

Any suggestion, correction or articles related that helps are super welcome! See you tomorrow

It's really cool what you're doing, not many people are comfortable sharing their journey online. I hope you continue this and I'll (and the wider OpenZeppelin community) will try our best to help you :slight_smile:

Can you share the code snippet for this?

I'm overriding _transfer from the openzeppeling ERC20 token. I'm introducing a potential flaw to attack?

It depends on what you're doing in your _transfer function and if there are any unsafe external contract calls.

    uint256 treasuryPercentage = 2; //used in _mintTreasury as percentage that goes to treasury on each transfer
    
    function defineTreasuryPercentaje(uint256 _newTreasuryPercentage) public onlyOwner {
        treasuryPercentage = _newTreasuryPercentage;
    }

    function _mintTreasury(uint256 _value) internal {
        _mint(msg.sender, _value * treasuryPercentage /100 ); //TODO CHANGE WITH TREASURY ADDRESS
    }

The idea of treasuryPercentage is to initialize the value that will then be updated with defineTreasuryPercentage (onlyOwner) function when defined with community.

I'm just adding 2 mint automated functions (to treasury and to miners). It shouldn't be a problem but since I know that I have to keep these overrides to the minimum possible, I became concerned about.

    function _mintTreasury(uint256 _value) internal {
        _mint(msg.sender, _value * treasuryPercentage /100 ); //TODO CHANGE WITH TREASURY ADDRESS
    }
   
    function _mintMinerReward() internal {
        _mint(block.coinbase, 1000);
    }

    function _transfer (address from, address to, uint256 value) internal override {
        _mintMinerReward();
        _mintTreasury(value);
        super._transfer(from, to, value);
    }

Thank you very much for your reply, Simon! I think the paradigm of working alone and being cautious about who to share code with has changed, especially with the decentralized approach that technology brings.
Have a great day :green_heart:

Day 2:
. Completed level 7 of Ethernaut.
. I was reviewing treasury and governance contracts. Mostly their logic and the functions I did not understand I read their documentation. I am really interested in quadratic voting.
. I spent time reading documentation about encodepacked and cryptographic hashing.
. Created a special repository on Github ( https://github.com/perassijulian/Omens )

DOUBTS:
. Have openzeppelin crowdsale contracts been discontinued? As they are neither in 0.3.x nor in 0.4.x I guess they will not be added again, does anyone know if this was due to security issues or why? What would be the best option to make these contracts, use as a guide the ones in 0.2.X or is there a better alternative in some website?

The idea of treasuryPercentage is to initialize the value that will then be updated with defineTreasuryPercentage (onlyOwner) function when defined with community.

If this is your usecase then your design is fine, you don't have to initialize it in the constructor since you also want a function to possibly update the treasuryPercentage in the future.

I'm just adding 2 mint automated functions (to treasury and to miners). It shouldn't be a problem but since I know that I have to keep these overrides to the minimum possible, I became concerned about.

These 2 mints look fine since they're not external contract calls.

1 Like

Day 3:

-Treasury : After reviewing several codes with hundreds of lines, which added unnecessary complexity for my use case, I found a video from EatTheBlocks that solved my problem in a super simple way. Now that I know how to solve it, I realize that I could have easily thought of it myself. Newbie.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

contract Treasury {
    
    uint public constant duration = 365 days;
    uint public immutable end;
    address payable public immutable owner;
    
    constructor(address payable _owner) {
        end = block.timestamp + duration;
        owner = _owner;
    }
    
    function deposit(address token, uint amount) external {
        IERC20(token).transferFrom(msg.sender, address(this), amount);
    }
    
    receive() external payable {}
    
    function withdraw(address token, uint amount) external {
        require(msg.sender == owner, 'only owner');
        require(block.timestamp >= end, 'too early');
        if (token == address(0)) {
            owner.transfer(amount);
        } else {
            IERC20(token).transfer(owner, amount);
        }
    }
}

To do:

  • Import Ownable from OpenZeppeling.
  • Modify the logic of the code since the idea is that the funds are unlocked at a certain percentage quarterly.

Doubts:

function withdraw(address token, uint amount) external {
        require(msg.sender == owner, 'only owner');
        require(block.timestamp >= end, 'too early');
        if (token == address(0)) {
            owner.transfer(amount);
        } else {
            IERC20(token).transfer(owner, amount);
        }
    }
  • Why did he do this?
        if (token == address(0)) {
            owner.transfer(amount);

. Governance token : again, after analyzing different codes I found one that models in a simple way the voting and uses quadratic voting. What the code does is a mapping where it has detailed balances of the different wallets. At no point does it generate a token, it just models the voting.

Doubts:

  • Should the token be generated in the same contract that contains the voting rules or you could make a call balanceOf(address account) to another contract and know how many votes the person can perform?
  • The only functionality of the governance token is to allow voting on different proposals?

. I've been watching a16z crypto startup school

. I've tried to dig on test-helpers but I've got some troubles installing hardhat. I know that when working with nodes this is a super common stuff but since we're testing security and that the vulnerability is 'high' I want to double check.

After npm install --save-dev @nomiclabs/hardhat-web3 web3

. https://www.npmjs.com/advisories/1654

Should I pay attention to this or just keep going?

Thank you for reading,
Any feedback, suggestion or improvement is welcome :slight_smile:

See you tomorrow.

  • Why did he do this?

To differentiate between an ETH transfer and a token transfer since ETH is not actually an ERC20.

  • Should the token be generated in the same contract that contains the voting rules or you could make a call balanceOf(address account) to another contract and know how many votes the person can perform?

Both, if implemented correctly, should achieve the same functionality however it makes more sense from a seperations of concern pov that you separate the governance token into its own contract. Imagine if other users want to interact with your governance token but have to go through the governance contract instead of the token directly.

  • The only functionality of the governance token is to allow voting on different proposals?

This is up to you.

I've tried to dig on test-helpers but I've got some troubles installing hardhat.

I think the guide is outdated. If you look on hardhat's website, it says npm i -D hardhat to install hardhat.

1 Like

Day 4:

After 2 days of installing and uninstalling packages, even re-installing nodejs, trying to get hardhat working without success: I've decided to start testing with truffle.
I started with the token contract. Basic tests were failing (I still don't know why) so I figured the right way to go would be leaving the base code, check that all tests pass and then add features and check that they pass as well.
The basic features and transfers work, the problem starts when adding approvals.

The snippet of the failing test is

it('approvals: msg.sender approves accounts[1] of 100 & withdraws 20 once.', async () => {
		    const balance0 = noDecimals(await this.token.balanceOf.call(accounts[ 0 ]));
		    assert.strictEqual(balance0.toString(), _inicialSupply.toString());

		    await this.token.approve(accounts[ 1 ], 100 * 10**_decimals, { from: accounts[ 0 ] }); // 100
		    const balance2 = noDecimals(await this.token.balanceOf.call(accounts[ 2 ]));
		    assert.strictEqual(balance2.toString(), '0', 'balance2 not correct');

		    await this.token.transferFrom.call(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ] });
		    await this.token.allowance.call(accounts[ 0 ], accounts[ 1 ]);
		    await this.token.transferFrom(accounts[ 0 ], accounts[ 2 ], 20, { from: accounts[ 1 ] }); // -20
		    const allowance01 = await this.token.allowance.call(accounts[ 0 ], accounts[ 1 ]);
		    assert.strictEqual(allowance01.toString(), '80'); // =80

		    const balance22 = noDecimals(await this.token.balanceOf.call(accounts[ 2 ]));
		    assert.strictEqual(balance22.toString(), '20');

		    const balance02 = noDecimals(await this.token.balanceOf.call(accounts[ 0 ]));
		    assert.strictEqual(balance02.toString(), '9980');
		});

The thing is this.token.balanceOf() returns values with their decimals. Then, when I try to use approve() with decimals I've got

Error: overflow (fault="overflow", operation="BigNumber.from", value=100000000000000000000, code=NUMERIC_FAULT, version=bignumber/5.0.8)`

I though that the function this.token.balanceOf() would return the value without decimals but apparently not. I'm gonna do some research with chai bignumbers package.
I'm happy that at least I'm running some tests after this long days tho.

Any feedback or recommendation would be super appreciate.

See you tomorrow

Day 5:

I managed to solve the handling of big numbers with the web3 library: web3.utils.toBN() . Creating an auxiliary function that not only performs the conversion to BN but also adds the decimal numbers:

function decimals(_value) {
		return web3.utils.toBN(_value * 10 **_decimals);
	}

Finally, I ran the tests again and they passed.

The next step in the code I am using as a guide checks that the events are correctly recorded in their logs. I've been reading a lot about how events are logged. This article is kind of an introduction that talks mostly about EVM and this one focuses mostly on the low-level part. They are both recommended but I would advice to dig into the next one as it is the most complete and approaches the subject from many angles.

Regarding to code, the one that's getting me stuck is

  it('events: should fire Transfer event properly', async () => {
    const res = await token.transfer(accounts[ 1 ], '2666', { from: accounts[ 0 ] })
    const transferLog = res.logs.find(
      element => element.event.match('Transfer') &&
        element.address.match(token.address)
    )
    assert.strictEqual(transferLog.args._from, accounts[ 0 ])
    // L2 ETH transfer also emits a transfer event
    assert.strictEqual(transferLog.args._to, accounts[ 1 ])
    assert.strictEqual(transferLog.args._value.toString(), '2666')
  })

I was reading ethers documentation but it solves it in a much more complex way.
Anyone knows which is the package that it uses or where I can read the documentation of that one?

I think that it was enough for today,

See you tomorrow

1 Like

balanceOf doesn't return decimals so there's no need to do noDecimals(). The reason why your test is failing is because of this line await this.token.approve(accounts[ 1 ], 100 * 10**_decimals, { from: accounts[ 0 ] }); // 100, specifically the 100 * 10**_decimals part because javascript has an integer limitation (see javascript Number.MAX_SAFE_INTEGER).

You should use bignumber to handle EACH individual value to be multiplied instead of this

function decimals(_value) {
	return web3.utils.toBN(_value * 10 **_decimals);
}

as it is prone to overflow as well.

1 Like

Where are you getting stuck at? How to verify logs?

1 Like

I've realized that it's because my token constructor is

    constructor () ERC20("Omens", "OMN") {
        _mint(msg.sender, 1000000 * (10 ** uint256(decimals())));
    }

It's weird because I've taken it from the contract wizard, but I've double checked and on the docs it's just

constructor(uint256 initialSupply) ERC20("Gold", "GLD") {
        _mint(msg.sender, initialSupply);
    }

Nevermind, I'm going to leave the constructor without decimals() and problem solved. Should be something to fix or it's ok as it is? @abcoathup

I though that it was something different than the way that I was heading.

The thing is that in the base code that I'm following it uses _from, _to and _value . If I change them for from, to and value it runs ok. Does it means that it's not firing it correctly or that the code that I follow is not updated? Do you know?

it('events: should fire Transfer event properly', async () => {
    const res = await token.transfer(accounts[ 1 ], '2666', { from: accounts[ 0 ] })
    const transferLog = res.logs.find(
      element => element.event.match('Transfer') &&
        element.address.match(token.address)
    )
    assert.strictEqual(transferLog.args._from, accounts[ 0 ])
    // L2 ETH transfer also emits a transfer event
    assert.strictEqual(transferLog.args._to, accounts[ 1 ])
    assert.strictEqual(transferLog.args._value.toString(), '2666')
  })

Thank you so much. You have no idea how much you're helping me :green_heart:

Ah ok I get the problem now. Yes, balanceOf returns a value that is multiplied by decimals. You need to remove the decimals and make it human readable using something like ethers.utils.formatUnits.

You should most definitely not remove it because the whole point of multiplying it by decimals is because solidity can only handle integers.

You should print out the entire res to understand what is being returned then you'll be able to answer your own question :slight_smile:

Day 6:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';


contract Treasury {
    
    uint public constant duration = 365 days;
    uint public immutable end;
    address payable public immutable owner;
    
    constructor(address payable _owner) {
        end = block.timestamp + duration;
        owner = _owner;
    }
    
    function deposit(address token, uint amount) external {
        IERC20(token).transferFrom(msg.sender, address(this), amount);
    }
    
    receive() external payable {}
    
    function withdraw(address token, uint amount) external {
        require(msg.sender == owner, 'only owner');
        require(block.timestamp >= end, 'too early');
        if (token == address(0)) {
            owner.transfer(amount);
        } else {
            IERC20(token).transfer(owner, amount);
        }
    }
}
  • I've made the main test to check that you can transfer Omens and Ether and it locks them for 1 year.
const { 
  expectRevert, 
  time, 
  constants 
} = require('@openzeppelin/test-helpers'); 

const Treasury = artifacts.require('Treasury.sol');
const OmensToken = artifacts.require('OmensToken.sol');

contract('Treasury', async (accounts) => {
	let treasury, token;
	const [deployer, owner, otherAddress, _] = accounts;

	beforeEach(async () => {
		treasury = await Treasury.new(owner);
		token = await OmensToken.new(web3.utils.toWei('1'));
	});

	it('should lock and unlock token and ether', async () => {
		let contractEtherBalance, contractTokenBalance, ownerEtherBalance, ownerTokenBalance;
		const etherAmount = web3.utils.toWei('1');
		const tokenAmount = web3.utils.toWei('1');

		await web3.eth.sendTransaction({
			from: owner,
			to: treasury.address,
			value: etherAmount
		});

		await token.approve(treasury.address, tokenAmount);
		await treasury.deposit(token.address, tokenAmount);
		contractEtherBalance = await web3.eth.getBalance(treasury.address);
		contractTokenBalance = await token.balanceOf(treasury.address);

		assert.strictEqual(contractEtherBalance.toString(), etherAmount);
		assert.strictEqual(contractTokenBalance.toString(), tokenAmount);

		await expectRevert(
			treasury.withdraw(token.address, tokenAmount, {from: otherAddress}),
			'only owner'
		);

		await expectRevert(
			treasury.withdraw(token.address, tokenAmount, {from: owner}),
			'too early'
		);

		await time.increase(time.duration.years(1));
		await treasury.withdraw(constants.ZERO_ADDRESS, etherAmount, {from: owner});
		await treasury.withdraw(token.address, tokenAmount, {from: owner});
		contractEtherBalance = await web3.eth.getBalance(treasury.address);
		contractTokenBalance = await token.balanceOf(treasury.address);
		ownerEtherBalance = await web3.eth.getBalance(owner);
		ownerTokenBalance = await token.balanceOf(owner)
		assert.strictEqual(contractEtherBalance.toString(), '0');
		assert.strictEqual(contractTokenBalance.toString(), '0');
		assert.strictEqual(ownerTokenBalance.toString(), tokenAmount);
		assert.strictEqual(ownerEtherBalance.toString().length, 20);
		assert.strictEqual(ownerEtherBalance.toString().slice(0, 2), '99');
	});
}); 
  • Finally, I've added the lines on the token that transfers a percentage on each transaction to the treasury. Need to make tests on the tokenTest
uint256 public treasuryPercentage = 2; //used in _mintTreasury as percentage that goes to treasury on each transfer
    address public treasuryAddress = 0xc52416eeED3942e7A1a6A9b4f3eCd437d3bC034F;


    function changeTreasuryPercentaje(uint256 _newTreasuryPercentage) public onlyOwner {
        treasuryPercentage = _newTreasuryPercentage;
    }

    function changeTreasuryAddress(uint256 _newTreasuryAddress) public onlyOwner {
        treasuryAddress = _newTreasuryAddress;
    }

    function _mintTreasury(uint256 _value) internal {
        _mint(treasuryAddress, _value * treasuryPercentage /100 );
    }

    function _transfer (address from, address to, uint256 value) internal override {
        _mintTreasury(value);
        super._transfer(from, to, value);
    }

Day 7:

I've been doing mostly reading today

Any reading suggestion will be appreciated.

Day 8:

After a few days with lack of concentration I've decided to jump in again to code.
I've advanced on the foundations of the governance token using the compound one as guide. I've just built the delegation system taking in account what happens when there's some changes on the delegatee

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GovToken is ERC20, Ownable {

/// @notice A checkpoint for marking number of votes from a given block
    struct Checkpoint {
        uint32 fromBlock;
        uint256 votes;
    }

    /// @notice A record of votes checkpoints for each account, by index
    mapping (address => mapping (uint32 => Checkpoint)) public checkpoints;

    /// @notice The number of checkpoints for each account
    mapping (address => uint32) public numCheckpoints;

    /// @notice A record of each accounts delegate
    mapping (address => address) public delegates;

    /// @notice An event thats emitted when an account changes its delegate
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    /// @notice An event thats emitted when a delegate account's vote balance changes
    event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance);


    constructor (uint256 initialSupply) ERC20("GovToken", "GOV") {
        _mint(msg.sender, initialSupply);
    }

    function delegate(address delegatee) public {
        return _delegate(msg.sender, delegatee);
    }

    function _delegate(address delegator, address delegatee) internal {
        address currentDelegate = delegates[delegator];
        uint256 delegatorBalance = this.balanceOf(delegator);
        delegates[delegator] = delegatee;

        emit DelegateChanged(delegator, currentDelegate, delegatee);

        _moveDelegates(currentDelegate, delegatee, delegatorBalance);
    }

    // remove old delegate and its votes and actualize DB with the new one
    function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {

        if (srcRep != dstRep && amount > 0) {
            if (srcRep != address(0)) {
                uint32 srcRepNum = numCheckpoints[srcRep];
                uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
                uint256 srcRepNew = srcRepOld - amount;
                _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
            }

            if (dstRep != address(0)) {
                uint32 dstRepNum = numCheckpoints[dstRep];
                uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
                uint256 dstRepNew = dstRepOld + amount;
                _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
            }
        }
    }

    function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint256 oldVotes, uint256 newVotes) internal {
        uint32 blockNumber = safe32(block.number, "Comp::_writeCheckpoint: block number exceeds 32 bits");
        if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
            checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
        } else {
            checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
            numCheckpoints[delegatee] = nCheckpoints + 1;
        }
        // actual values are stored in ( n - 1 )
      emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
    }

    function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {
        require(n < 2**32, errorMessage);
        return uint32(n);
    }
}

Regarding to tests it passes all the ones made for the primary token

And a big milestone :triangular_flag_on_post: is that I've written my first 2 tests that I didn't check any documentation or followed any examples. Feels suuuper cool. Those are

it('should delegate properly', async() => {

			// address
			await this.token.delegate(accounts[1], {from: accounts[0]});
			const delegatee = await this.token.delegates.call(accounts[0]);
			assert.strictEqual(delegatee, accounts[1]);

			//amount 
			balance0 = await this.token.balanceOf(accounts[0]);
			chPoint1 = await this.token.numCheckpoints.call(accounts[1]);
			votes1 = await this.token.checkpoints.call(accounts[1], chPoint1 - 1);
			assert.strictEqual(balance0.toString(), votes1.votes.toString());

		});

		it('should actualizate delegatee when delegate to a new one', async() => {
			
			balance0 = await this.token.balanceOf(accounts[0]);

			// first delegate	
			
			await this.token.delegate(accounts[1], {from: accounts[0]});
			var delegatee1 = await this.token.delegates.call(accounts[0]);
			assert.strictEqual(delegatee1, accounts[1]);

			var chPoint1 = await this.token.numCheckpoints.call(accounts[1]);
			var votes1 = await this.token.checkpoints.call(accounts[1], chPoint1 - 1);
			assert.strictEqual(balance0.toString(), votes1.votes.toString());

			// second delegate

			await this.token.delegate(accounts[2], {from: accounts[0]});
			var delegatee2 = await this.token.delegates.call(accounts[0]);
			assert.strictEqual(delegatee2, accounts[2]);

			var chPoint2 = await this.token.numCheckpoints.call(accounts[2]);
			var votes2 = await this.token.checkpoints.call(accounts[2], chPoint2 - 1);
			assert.strictEqual(balance0.toString(), votes2.votes.toString());

			// check remotion from account 1

			var chPoint1 = await this.token.numCheckpoints.call(accounts[1]);
			var votes1 = await this.token.checkpoints.call(accounts[1], chPoint1 - 1);
			assert.strictEqual(votes1.votes.toString(), '0');

		});

And they passed as well

image

//TODO: what would happen if 0 delegates to 1 and 1 delegates to 2 ?
//TODO: what would happen if 0 delegate to 1 and 0 transfer to another account (regarding 1 amount of votes)

If you have more ideas for testing they will be appreciated.

See you tomorrow!