Writing a Smart Contract that takes and gives Ether on Remix [PT1]

Project Parameters:

  1. Use a Crypto Oracle to get the Price of Ethereum in US Dollars
  2. Create functions that allow for a user to deposit 500 dollar minimum of Ether to our Contract
  3. Create functions that allow for the user to withdraw money from the function
  4. Write the original contract in Remix, then transition the project to a truffle project, and write tests migrations, and deploy it to a Testnet from VE

Ok that’s pretty general but this is way different than a puny state change let’s go:

Thinking out loud let’s start with parameter number one, what will we need?

  1. For starters we’ll need to use a crypto oracle, let’s use chainlink
  2. We’ll have to figure out how to calculate the price of Ethereum based on the information in the oracle
  3. Create a payable function that allows the user to deposit a given number of ether and hold it.

Let’s start with implementing the price-getting functionality. Let’s start with the Chainlink documentation on getting the latest price.

If you want to implement the get price functionality in your contract you’re first going to have to import the contract itself. The top of your contract should look like the following

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

What the import statement is doing is that it’s actually referencing the chainlink Github and getting the contract that will allow us to fetch the price.

Now onto our actual contract for now we will be working in remix and move it over to a truffle project. So let’s start whipping up the function to get the price. I’ll explain it line by line below.

contract getPaid {
address payable public owner;
address[] public funders;
constructor() {
owner = payable(msg.sender);
function getThePrice() public view returns (uint256) {
//note that the contract address used is the ETH / USD price from the Kovan TestNet
AggregatorV3Interface priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
(,int256 price,,,) = priceFeed.latestRoundData();
return uint256(price);
}
}

A little note on the variables and the constructor:

  1. the first variable is a null variable that will then be set to the address of whatever person is interacting with the contract.
  2. Then the funders array is an array that only stores addresses in it that will be used when we need to implement withdraw functionality.
  3. If you’re using V8 Compiler than don’t forget to make the owner variable payable because in version 8 msg.sender isn’t automatically payable

Alright, so this is a powerful little function that fetches the price for us every time we call it once it’s deployed:

  1. The Function declaration line declares the function name as well as the type of the function, it’s public meaning it can be called by anybody, it doesn’t change state so it’s a view function, and lastly, it returns the data type uint256 [Unsigned integer 256 bits].
  2. Then the priceFeed variable is declared and you see it’s of the type AggregatorV3Interface, The Fuck? Well, you can actually declare new Objects from imported interfaces in our case because if you look at the solidity file here you see the file starts with Interface. TF is an interface? Think of it as a contract with pre-loaded half-baked functions, half-baked in the sense that you have to tell solidity explicitly how you want it to interact with those functions.
  3. The PriceFeed variable is set equal to an instance of the interface that points to a contract present on the Kovan Test Net that monitors the pair ETH / USD. [Do you see what I meant by half-baked? we had to tell the interface WHAT contract address to interact with]
  4. Then we have this weird piece of code that starts with a parenthesis a comma followed by the data type int256 price and 3 more commas. So if you go to the contract and go to the bottom and look at the function ‘latestRoundData’ you see that the function has 5 return values, and all of them have various information like RoundID when it was updated, and other things but for our cases, we just want the price. You may be asking Sonny why can’t we just set priceFeed equal to int256 price. You would be able to do that if the function only returned one value, but since it returns 5 we need the commas along with the value that we’re returning to add to 5. Since we don’t care to return them we don’t need to fill them out we just need the commas so it knows that we want the price.
  5. then we type coerce the value to a 256-bit unsigned integer and return it. Now if you deploy it on Kovan or whatever testnet you’re using you will see a number that’s way longer than what you anticipated. If you go to the chainlink documentation for each contract address for each pair has a number of decimals that it says it returns so in our case it returns 8 decimal points so if you count 8 figures to the right you get the price in USDs.

Now that we’ve created the price fetching function we can now use that to enforce a minimum deposit value for our smart contract, let’s choose 100 USD so anything less than that will be rejected by our contract. But we’re going to need a second helper function that will convert the price given into Actual dollar-denominated terms

  1. Let’s think about this first before where I said things are denominated to 8 decimal places so if we divide by 10 ^ 8 we should get it to the decimal in the right place where we have a dollar conversion. Let’s code this up
function ConvertPriceToUSD() public view returns (uint256){
uint256 currPrice = getThePrice();
return  (currPrice / 10 ** 8);
//this should move the decimal place enough to get a denomination of ETH in dollar terms
}

Now that we have a way to get the current price is USDs which we can use later to compare the amount of deposited ETH in the funding function below. Also make sure to declare these two values at th:

function deposit() public payable {
uint256 minimumAmt = 100 * 10  ** 18;
require(msg.value >= minimumAmt, "Deposit more fool");
addressToDollarAmt[msg.sender] += msg.value;
funders.push(msg.sender);
}

Alright let’s break down what this function does line by line:

  1. So on line one you see a new keyword and that is payable, that’s our way of telling the Solidity compiler that this function deals with the exchange of money in some capacity.
  2. We then declare a function bound variable that sets a minimum threshold to be tested in the line below, we multiply it by 10 ^ 18 to put it in terms of WEI
  3. We then have a require statement below that says 100 wei is the minimum threshold for the contract, and if they deposit less than that they get an error message.
  4. Then this address gets pushed to the funders array.

Awesome so we’ve implemented deposit functionality for multiple users, but what if our users wanted to withdraw money from the contract, and how do we make sure that ONLY they can withdraw the money that THEY put in, and not anyone else’s ETH?

We’re going to need a few things the first thing is a modifier and the second is the actual withdraw function itself:

modifier onlyOwner {
require(msg.sender == owner);
_;
}
function withdraw() public payable onlyOwner {
payable(msg.sender).transfer(address(this).balance);
for (uint256 funderI=0; funderI < funders.length; funderI++) {
address withdrawlAddy = funders[funderI];
addressToDollarAmt[withdrawlAddy] = 0;
}
funders = new address[](0);
}
  1. Firstly we start with our modifier which is simply just an extra required parameter we can use for our withdrawal function. Don’t miss the “_” so that an exception is thrown instead of you’re contract breaking.
  2. Then as you see on the declaration line we use the modifier only owner, then as you go to the second line we make the msg.sender variable of type variable and then we call the transfer method to transfer the balance of the contract back to the address interacting with the contract.
  3. Don’t forget we also have to update the state of the array to ensure the security of our contract. So we start a loop to loop over the funders array and we set all the values in the array to 0 [so only the array containing the address of the person who’s interacting with it], and then starts a new funders array and sets all the values to 0 in that new array.

Now that you’ve finished the contract it is now time to move out of kushy sandbox that is remix and port it over to truffle. If you have any unresolved bugs check this GitHub Repo.

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also Read


Writing a Smart Contract that takes and gives Ether on Remix [PT1] was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.