Imagine that you want to deploy a SC within deployments on github without compromising your private and api keys? This is accessable through github actions.
PROS
- Security
- Easy to work with teams
- Apply test cases before deployments
CON
- Hard to verify
First let's use a simple token lock as example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
}
contract TokenLockVault {
struct Lock {
uint256 amount;
uint256 unlockTime;
}
mapping(address => mapping(address => Lock)) public locks;
event TokensLocked(address indexed user, address indexed token, uint256 amount, uint256 unlockTime);
event TokensWithdrawn(address indexed user, address indexed token, uint256 amount);
function lockTokens(address token, uint256 amount, uint256 duration) external {
if (amount == 0) revert("Amount cannot be zero");
if (duration == 0) revert("Duration must be greater than zero");
uint256 unlockTime = block.timestamp + duration;
locks[msg.sender][token] = Lock(amount, unlockTime);
bool success = IERC20(token).transferFrom(msg.sender, address(this), amount);
if (!success) revert("Token transfer failed");
emit TokensLocked(msg.sender, token, amount, unlockTime);
}
function withdrawTokens(address token) external {
Lock memory userLock = locks[msg.sender][token];
address locker = msg.sender;
if (userLock.amount == 0) revert("No tokens locked");
if (block.timestamp < userLock.unlockTime) revert("Tokens are still locked");
delete locks[locker][token];
bool success = IERC20(token).transfer(locker, userLock.amount);
if (!success) revert("Token transfer failed");
emit TokensWithdrawn(msg.sender, token, userLock.amount);
}
}
With this test case scenario:
import hre from "hardhat";
import { token, TokenLockVault } from "../typechain-types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { time } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
describe("Testing token lock", function () {
let tokenLocker: TokenLockVault;
let ethus: Token;
let owner: HardhatEthersSigner, locker: HardhatEthersSigner
beforeEach(async () => {
[owner, locker ] = await hre.ethers.getSigners();
tokenLocker = await hre.ethers.deployContract("TokenLockVault") as EthusTokenLockVault;
ethus = await hre.ethers.deployContract("token") as token;
console.log("Deploying contracts...");
// Ensure contracts are initialized correctly
await token.connect(owner).initialize(owner.address);
console.log("Contracts deployed and initialized successfully");
// Transfer tokens to liquidity providers
const balance0 = await token.balanceOf(owner.address);
await token.transfer(locker.address, balance0 / BigInt(2));
});
it("Should lock tokens", async () => {
const balance = await ethus.balanceOf(locker.address);
const TEN_DAYS_IN_SECONDS = 10 * 24 * 60 * 60;
const unlockTime = (await time.latest()) + TEN_DAYS_IN_SECONDS;
await ethus.connect(locker).approve(await tokenLocker.getAddress(), balance);
await tokenLocker.connect(locker).lockTokens(await ethus.getAddress(), balance, TEN_DAYS_IN_SECONDS);
const lock = await tokenLocker.connect(locker).locks(locker.address, ethus.getAddress());
expect(lock[0]).to.equal(balance);
expect(lock[1]).to.be.closeTo(unlockTime, 2);
})
it("should revert if we tray to unlock tokens before the unlock time", async () => {
const balance = await token.balanceOf(locker.address);
const TEN_DAYS_IN_SECONDS = 10 * 24 * 60 * 60;
await token.connect(locker).approve(await tokenLocker.getAddress(), balance);
await tokenLocker.connect(locker).lockTokens(await ethus.getAddress(), balance, TEN_DAYS_IN_SECONDS);
await expect(tokenLocker.connect(locker).withdrawTokens(await ethus.getAddress())).to.be.revertedWith("Tokens are still locked");
})
it("Should allow withdrawner to withdraw tokens after the unlock time", async () => {
const balance = await token.balanceOf(locker.address);
const TEN_DAYS_IN_SECONDS = 10 * 24 * 60 * 60;
const unlockTime = (await time.latest()) + TEN_DAYS_IN_SECONDS;
await token.connect(locker).approve(await tokenLocker.getAddress(), balance);
await tokenLocker.connect(locker).lockTokens(await ethus.getAddress(), balance, TEN_DAYS_IN_SECONDS);
await time.increase(TEN_DAYS_IN_SECONDS + 1);
await tokenLocker.connect(locker).withdrawTokens(await ethus.getAddress());
const lock = await tokenLocker.connect(locker).locks(locker.address, ethus.getAddress());
expect(lock[0]).to.equal(0);
expect(lock[1]).to.equal(0);
})
});
Let's breakdown the github workflow YML:
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up AWS CLI
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.SECRET_KEY }}
aws-region: us-east-1
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "22" # Ensure this matches your local setup
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Retrieve PRIVATE_KEY from AWS Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: PRIVATE_KEY
parse-json-secrets: false
- name: Set Hardhat Vars Before Tests
run: |
echo "PRIVATE_KEY=$PRIVATE_KEY" >> $GITHUB_ENV # Set PRIVATE_KEY as an environment variable
npx hardhat vars set PRIVATE_KEY $PRIVATE_KEY # Directly pass the value of PRIVATE_KEY
npx hardhat vars set ALCHEMY_POLYGON_MAINNET_KEY ${{ secrets.ALCHEMY_POLYGON_MAINNET_KEY }}
npx hardhat vars set POLYGONSCAN_API_KEY ${{ secrets.POLYGONSCAN_API_KEY }}
- name: Run Hardhat Tests
run: npx hardhat test
In this part we extract the private key from the deployer wallet from aws secret manager.
eploy:
name: Deploy Smart Contract
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/locker'
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up AWS CLI
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.SECRET_KEY }}
aws-region: us-east-1
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "22"
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Retrieve PRIVATE_KEY from AWS Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: PRIVATE_KEY
parse-json-secrets: false
- name: Set Hardhat Vars Before Deploy
run: |
echo "PRIVATE_KEY=$PRIVATE_KEY" >> $GITHUB_ENV # Set PRIVATE_KEY as an environment variable
npx hardhat vars set PRIVATE_KEY $PRIVATE_KEY
npx hardhat vars set ALCHEMY_POLYGON_MAINNET_KEY ${{ secrets.ALCHEMY_POLYGON_MAINNET_KEY }}
npx hardhat vars set POLYGONSCAN_API_KEY ${{ secrets.POLYGONSCAN_API_KEY }}
- name: Deploy Smart Contract
run: echo "y" | npx hardhat ignition deploy ignition/modules/Lock.ts --network polygon --verify
- name: Verify Deployed Smart Contract
run: |
npx hardhat ignition verify chain-137 --include-unrelated-contracts
Right after we use vars set from hardhat to set variables extracted from both github secrets and AWS secret manager the hardhat enviroment and proceed to deploy and verfify the smartcontracts.
My last comments, i really don't think this is the best approach to handle public smartcontracts because it's hard to verify. At deployment time the scan didn't indexed the smartcontract.
But it's okey to corporative, testnet or depending to the usecase of your smartcontract.
Top comments (0)