챌린지를 수행하며 기억에 남은 내용만 간단히 회고한다.
Solidity 사용이 처음이었으므로 다음과 같이 velog에 기록해두고 참고하며 계약을 작성했다.
https://velog.io/@oomia/Solidity
최대한 다양한 구성 요소를 활용하여 깔끔하게 작성하려 노력했다. 그 결과는 다음과 같다.
https://gist.github.com/ooMia/99f6c91e3ddd6d0f313806e17dc809fb
interface
에 정의external
이어야만 한다external
함수는 내부에서 호출할 수 없다.interface IStaker {
// +---------------------+
// | Function (external) |
// +---------------------+
// Receives eth and calls stake()
receive() external payable;
// After some `deadline` allow anyone to call an `execute()` function, just once
// If the deadline has passed and the threshold is met
// It should call `exampleExternalContract.complete{value: address(this).balance}()`
function execute() external;
// If the deadline has passed and the `threshold` was not met,
// allow everyone to call a `withdraw()` function to withdraw their balance
function withdraw() external;
}
public
으로 선언하고, abstract
에 정의virtual
로 정의event
도 정의할 수 있었지만, 반드시 상속시킬만한 이유가 없어 구현으로 넘김is IStaker
을 통해 인터페이스 상속abstract contract _Staker is IStaker {
// +-------------------+
// | Function (public) |
// +-------------------+
// Collect funds in a payable `stake()` function and track individual `balances` with a mapping:
// Make sure to emit `Stake(address,uint256)` event for the frontend `All Stakings` tab to display
function stake() public payable virtual;
// Add a `timeLeft()` view function that returns the time left before the deadline for the frontend
function timeLeft() public view virtual returns (uint256);
}
require
문을 사용하고, 원인에 대해서는 문자열을 사용한다.error
를 정의하고 if ... revert <error>
문으로 대체했다.contract Staker is _Staker {
// +----------------+
// | State Variable |
// +----------------+
ExampleExternalContract public exampleExternalContract;
mapping(address => uint256) public balances;
uint256 public threshold;
uint256 public deadline;
bool private openForWithdraw;
// +-------+
// | Event |
// +-------+
// Make sure to add a `Stake(address,uint256)` event and emit it for the frontend `All Stakings` tab to display)
event Stake(address indexed staker, uint256 amount);
// +-------+
// | Error |
// +-------+
error ShouldStakeMoreThanZero();
// +----------+
// | Modifier |
// +----------+
modifier onProceed() {
require(!exampleExternalContract.completed(), "Staking completed");
_;
}
modifier onTimeOut() {
require(isTimeOut(), "Wait for the contract to complete");
_;
}
modifier
를 사용한다.private view
로 정의하여 가독성을 높였다.private
하지 않은 모든 함수는 override
로 재정의해야 한다. // +-----------------------+
// | Function (implements) |
// +-----------------------+
constructor(address exampleExternalContractAddress) {
exampleExternalContract = ExampleExternalContract(
exampleExternalContractAddress
);
threshold = 0.0011 ether;
deadline = block.timestamp + 5 minutes;
}
receive() external payable override {
stake();
}
function execute() external override onProceed onTimeOut {
if (!openForWithdraw && isThresholdMet()) {
exampleExternalContract.complete{ value: address(this).balance }();
}
openForWithdraw = true;
}
function withdraw() external override onTimeOut {
require(openForWithdraw, "Run Execute first");
uint256 amount = balances[msg.sender];
if (amount <= 0) revert ShouldStakeMoreThanZero();
balances[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
function stake() public payable override onProceed {
if (msg.value <= 0) revert ShouldStakeMoreThanZero();
balances[msg.sender] += msg.value;
emit Stake(msg.sender, msg.value);
}
function timeLeft() public view override returns (uint256) {
if (block.timestamp >= deadline) {
return 0;
}
return deadline - block.timestamp;
}
// +--------------------+
// | Function (private) |
// +--------------------+
function isTimeOut() private view returns (bool) {
return timeLeft() == 0;
}
function isThresholdMet() private view returns (bool) {
return address(this).balance >= threshold;
}
}
배포 중 문제 하나는 결과적으로 etherscan
에 검증(verified)된 상태로 계약이 배포되어야 한다는 것이다. verify & push
라는 웹 사이트 기능을 활용하여 수동으로 작업해도 되지만, 라이브러리나 계약 작성에 다양한 계약들을 참조하면 사용하기 어려운 기능이다.
hardhat
환경에서는 이를 편리하게 해결할 수 있다.
https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#usage
npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1"
일반적인 환경에서의 문법은 위와 같으며, 본 프로젝트에서는 다음과 같이 정의되어 있다.
yarn hardhat-verify