Deploy a smart contract on Bubs testnet
In this tutorial, we will deploy a smart contract to the Bubs testnet.
Dependencies
- Foundry installed on your machine
- Node.js
- Basic understanding of Ethereum
- Basic understanding of Solidity and Node.js
- Bubs ETH from the Bubs faucet
- A Bubs RPC URL from the Bubs testnet page
Setup
First, in your $HOME directory, set up a new project folder for this tutorial and init the project with npm:
cd $HOME
mkdir counter-project && cd counter-project && npm init -ycd $HOME
mkdir counter-project && cd counter-project && npm init -yNext, initialize a Foundry project with the following command:
forge init counter_contractforge init counter_contractCreate your smart contract
Take a look at the Counter.sol file in your counter-project/counter_contract/src directory:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}The contract contains a public unsigned integer variable named "number". There are two public functions in this contract. The setNumber function allows anyone to set a new value for the "number" variable, while the increment function increases the value of "number" by one each time it's called.
You can learn more about Solidity and smart contract programming.
To compile the contract, run the following forge command from the $HOME/counter-project/counter_contract/ directory:
forge buildforge buildYour output should look similar to the following:
[⠢] Compiling...
[⠔] Compiling 21 files with 0.8.19
[⠑] Solc 0.8.19 finished in 1.24s
Compiler run successful[⠢] Compiling...
[⠔] Compiling 21 files with 0.8.19
[⠑] Solc 0.8.19 finished in 1.24s
Compiler run successfulTest your smart contract
Now, open the test/Counter.t.sol file:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
counter.setNumber(0);
}
function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testSetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter public counter;
function setUp() public {
counter = new Counter();
counter.setNumber(0);
}
function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testSetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
}This file performs unit testing on the contract we created in the previous section. Here's what the test is doing:
The contract includes a public "Counter" type variable called "counter". In the setUp function, it initializes a new instance of the "Counter" contract and sets the "number" variable to 0.
There are two test functions in the contract: testIncrement and testSetNumber.
The testIncrement function tests the "increment" function of the "Counter" contract by calling it and then asserting that the "number" in the "Counter" contract is 1. It verifies if the increment operation correctly increases the number by one.
The testSetNumber function is more generic. It takes an unsigned integer argument 'x' and tests the "setNumber" function of the "Counter" contract. After calling the "setNumber" function with 'x', it asserts that the "number" in the "Counter" contract is equal to 'x'. This verifies that the "setNumber" function correctly updates the "number" in the "Counter" contract.
Now, to test your code, run the following:
forge testforge testIf the test is successful, your output should be similar to this:
[⠆] Compiling...
No files changed, compilation skipped
Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testIncrement() (gas: 28334)
[PASS] testSetNumber(uint256) (runs: 256, μ: 27709, ~: 28409)
Test result: ok. 2 passed; 0 failed; finished in 8.96ms[⠆] Compiling...
No files changed, compilation skipped
Running 2 tests for test/Counter.t.sol:CounterTest
[PASS] testIncrement() (gas: 28334)
[PASS] testSetNumber(uint256) (runs: 256, μ: 27709, ~: 28409)
Test result: ok. 2 passed; 0 failed; finished in 8.96msDeploying your smart contract
Using Anvil
First, we'll test out our contract on a local devnet called "anvil". To start the local server, run:
anvilanvilYou'll see a local RPC endpoint (127.0.0.1:8545) and accounts to test with.
Let's deploy the contract now. First, set a private key from anvil:
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export ANVIL_RPC_URL=http://localhost:8545export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export ANVIL_RPC_URL=http://localhost:8545Now, deploy the contract:
forge create --rpc-url $ANVIL_RPC_URL \
--private-key $PRIVATE_KEY \
src/Counter.sol:Counterforge create --rpc-url $ANVIL_RPC_URL \
--private-key $PRIVATE_KEY \
src/Counter.sol:CounterUsing Bubs
First, set a private key from your funded Ethereum wallet and set the BUBS_RPC_URL variable with an RPC of your choosing:
export BUBS_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export BUBS_RPC_URL=https://bubs.calderachain.xyz/httpexport BUBS_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export BUBS_RPC_URL=https://bubs.calderachain.xyz/httpNow that we're ready to deploy the smart contract onto Bubs, we will run the forge create command.
forge create --rpc-url $BUBS_RPC_URL \
--private-key $BUBS_PRIVATE_KEY \
src/Counter.sol:Counterforge create --rpc-url $BUBS_RPC_URL \
--private-key $BUBS_PRIVATE_KEY \
src/Counter.sol:CounterA successful deployment will return output similar to below:
[⠆] Compiling...
No files changed, compilation skipped
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0xf1a793a793cd9fc588f5132d99008565ea361eb3535d66499575e9e1908200b2[⠆] Compiling...
No files changed, compilation skipped
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0xf1a793a793cd9fc588f5132d99008565ea361eb3535d66499575e9e1908200b2Once you've deployed the contract, you're ready to interact with it!
First, we'll set it as a variable:
export CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3export CONTRACT_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3Interacting with your smart contract
Foundry uses cast, a CLI for performing Ethereum RPC calls.
To write to the contract, we'll use the cast send command:
cast send $CONTRACT_ADDRESS "setNumber(uint256)" 10 --rpc-url $BUBS_RPC_URL --private-key $PRIVATE_KEYcast send $CONTRACT_ADDRESS "setNumber(uint256)" 10 --rpc-url $BUBS_RPC_URL --private-key $PRIVATE_KEYYour output will look similar:
blockHash 0x131822bef6eb59656d7e1387c19b75be667e587006710365ec5cf58030786c42
blockNumber 3
contractAddress
cumulativeGasUsed 43494
effectiveGasPrice 3767182372
gasUsed 43494
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x8f15d6004598f0662dd673a9898dceef77be8cc28408cecc284b28d7be32307d
transactionIndex 0
type 2blockHash 0x131822bef6eb59656d7e1387c19b75be667e587006710365ec5cf58030786c42
blockNumber 3
contractAddress
cumulativeGasUsed 43494
effectiveGasPrice 3767182372
gasUsed 43494
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x8f15d6004598f0662dd673a9898dceef77be8cc28408cecc284b28d7be32307d
transactionIndex 0
type 2Now, we can make a read call to view the state of the number variable, using the cast call command:
cast call $CONTRACT_ADDRESS "number()" --rpc-url $BUBS_RPC_URLcast call $CONTRACT_ADDRESS "number()" --rpc-url $BUBS_RPC_URLThe result will look similar:
0x000000000000000000000000000000000000000000000000000000000000000a0x000000000000000000000000000000000000000000000000000000000000000aConvert the result from hexadecimal to a base 10 value with:
echo $((0x000000000000000000000000000000000000000000000000000000000000000a))echo $((0x000000000000000000000000000000000000000000000000000000000000000a))Next steps
Congratulations! You've learned how to deploy a smart contract to Bubs testnet.
What will you build next? Now, you're ready to check out the GM Portal tutorial.