Smart Contract Example
Task
Create a smart contract of folowing points included
- User can create timebound transactions
- List all the transaction in one pool with reverted, executed and pending
- User can revert the transaction if the time has passed
- Third user or any other user in the pool( except the user who is receiving the transaction ) allow to execute the transaction can
- In transaction there should be amount included, the time period, tip for executing.
The solidity contract for this task is:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
contract TimeTransactions{
struct Transaction{
address creator;
address recipient;
uint256 amount;
uint256 createdAt;
uint256 executionTime;
uint256 tip;
Status status;
}
enum Status{
Pending,
Executed,
Reverted
}
event TransactionCreated(uint256 indexed transactionCount, address creator, address recipient,uint256 amount,uint256 executionTime, uint256 tip);
event TransactionReverted(uint256 indexed transactionId);
event TransactionExecuted(uint256 indexed transactionId, address executor);
mapping(uint256=> Transaction) public transactions;
uint256 public transactionCount;
function createTransaction(address _recipient, uint256 _amount, uint256 _executionTime, uint256 _tip) external payable{
require(_recipient != address(0), "Invalid Transaction");
require(_amount>0,"Amount must be greater than zero");
require(msg.value == _amount + _tip,"Sent value must match amount and tip");
require(_executionTime > block.timestamp,"Execution time must be in future");
transactions[transactionCount]= Transaction({
creator: msg.sender,
recipient: _recipient,
amount: _amount,
createdAt: block.timestamp,
executionTime: _executionTime,
tip: _tip,
status: Status.Pending
});
emit TransactionCreated(transactionCount, msg.sender, _recipient, _amount, _executionTime, _tip);
transactionCount++;
}
function RevertTransaction(uint256 _transactionId) external {
Transaction storage transaction= transactions[_transactionId];
require(transaction.creator == msg.sender,"Only creator can Revert the transaction");
require(transaction.status == Status.Pending,"Transaction not in pending status");
require(block.timestamp < transaction.executionTime, "Revert time experied");
transaction.status= Status.Reverted;
payable(msg.sender).transfer(transaction.amount + transaction.tip);
emit TransactionReverted(_transactionId);
}
function ExecuteTransaction(uint256 _transactionId) external{
Transaction storage transaction= transactions[_transactionId];
require(transaction.status== Status.Pending,"Transaction Not Pending");
require(transaction.recipient != msg.sender,"Executor cannot perform this task");
require(block.timestamp>= transaction.executionTime,"Transaction not yet executable");
transaction.status= Status.Executed;
payable(transaction.recipient).transfer(transaction.amount);
payable(msg.sender).transfer(transaction.tip);
emit TransactionExecuted(_transactionId, msg.sender);
}
function getTransaction(uint256 _transactionId) external view returns (
address creator,
address recipient,
uint256 amount,
uint256 createdAt,
uint256 executionTime,
uint256 tip,
Status status
) {
Transaction storage transaction = transactions[_transactionId];
return (
transaction.creator,
transaction.recipient,
transaction.amount,
transaction.createdAt,
transaction.executionTime,
transaction.tip,
transaction.status
);
}
function getAllTransactions() external view returns (Transaction[] memory) {
Transaction[] memory allTransactions = new Transaction[](transactionCount);
for (uint256 i = 0; i < transactionCount; i++) {
allTransactions[i] = transactions[i];
}
return allTransactions;
}
}
The test code is:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/TimeTransactions.sol";
contract TimeTransactionsTest is Test{
TimeTransactions public timetransactions;
address creator;
address recipient;
address executor;
function setUp() public{
timetransactions= new TimeTransactions();
creator= makeAddr("creator");
recipient= makeAddr("recipient");
executor= makeAddr("executor");
vm.deal(creator, 10 ether);
vm.deal(executor, 10 ether);
}
function testcreatTransaction() public{
uint256 executionTime= block.timestamp + 1 minutes;
uint256 amount= 0.5 ether;
uint256 tip= 0.1 ether;
vm.prank(creator);
timetransactions.createTransaction{value: amount + tip }(recipient, amount, executionTime, tip);
(address txCreator,,,,uint256 txExecutionTime,uint256 txTip ,) = timetransactions.getTransaction(0);
assertEq(txCreator, creator);
assertEq(txExecutionTime, executionTime);
assertEq(txTip,tip);
}
function testRevertTransaction() public{
uint256 executionTime= block.timestamp + 1 minutes;
uint256 amount = 0.5 ether;
uint256 tip = 0.1 ether;
vm.prank(creator);
timetransactions.createTransaction{value: amount + tip }(
recipient,
amount,
executionTime,
tip
);
vm.prank(creator);
timetransactions.RevertTransaction(0);
(, , , , , , TimeTransactions.Status status) = timetransactions.getTransaction(0);
assertEq(uint256(status), uint256(TimeTransactions.Status.Reverted));
}
function testExecutedTransaction() public {
uint256 executionTime= block.timestamp + 1 minutes;
uint256 amount = 0.5 ether;
uint256 tip = 0.1 ether;
vm.prank(creator);
timetransactions.createTransaction{value: amount + tip }(
recipient,
amount,
executionTime,
tip
);
vm.warp(executionTime + 1 minutes);
vm.prank(creator);
timetransactions.ExecuteTransaction(0);
(, , , , , , TimeTransactions.Status status) = timetransactions.getTransaction(0);
assertEq(uint256(status), uint256(TimeTransactions.Status.Executed));
}
function testInsufficientFunds() public {
uint256 executionTime = block.timestamp + 1 minutes;
uint256 amount = 0.5 ether;
uint256 tip = 0.1 ether;
vm.prank(creator);
vm.expectRevert("Sent value must match amount and tip");
timetransactions.createTransaction{value: amount}(
recipient,
amount,
executionTime,
tip
);
}
function testCannotExecuteBeforeExecutionTime() public {
uint256 executionTime = block.timestamp + 1 minutes;
uint256 amount = 0.5 ether;
uint256 tip = 0.1 ether;
vm.prank(creator);
timetransactions.createTransaction{value: amount + tip}(
recipient,
amount,
executionTime,
tip
);
vm.prank(executor);
vm.expectRevert("Transaction not yet executable");
timetransactions.ExecuteTransaction(0);
}
}
The deploy code is:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/TimeTransactions.sol";
contract DeployTimeTransactions is Script {
TimeTransactions public timetransactions;
function run() public {
vm.startBroadcast();
timetransactions = new TimeTransactions();
vm.stopBroadcast();
}
}
It is deployed in sepolia testnet using RPC_URL of sepolia and PRIVATE_KEY of my metamask account.