Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev aptos #28

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions atomicPool/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
node_modules
.env

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments
22 changes: 22 additions & 0 deletions atomicPool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# AtomicPool Contract

## Overview
The `AtomicPool` contract enables secure, time-locked fund locking and unlocking, integrating with the Layerswap V8 protocol.

## Features
- **Lock Funds**: Create a time-locked pool with a solver.
- **Unlock Funds**: Retrieve funds after the timelock expires.
- **Extend Timelock**: Increase the timelock duration.
- **Punish Solvers**: Penalize solvers for failing conditions using HTLC details.
- **Integration**: Works with Layerswap V8 for HTLC validation.

## Usage
1. **Deploy**: Pass the Layerswap V8 contract address.
2. **Lock**: Use `lockPool` to lock funds with a solver and timelock.
3. **Unlock**: Use `unlockPool` to retrieve funds after the timelock.
4. **Extend**: Use `extendPoolTime` to increase the timelock.
5. **Punish**: Use `punishSolver` with HTLC details and secret.

## Requirements
- **Solidity**: ^0.8.23
- **Dependencies**: OpenZeppelin `ReentrancyGuard`, Layerswap V8 Interface
164 changes: 164 additions & 0 deletions atomicPool/contracts/atomicPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// _ _ _ ____ _
// / \ | |_ ___ _ __ ___ (_) ___ | _ \ ___ ___ | |
// / _ \| __/ _ \| '_ ` _ \| |/ __| | |_) / _ \ / _ \| |
// / ___ \ || (_) | | | | | | | (__ | __/ (_) | (_) | |
// /_/ \_\__\___/|_| |_| |_|_|\___| |_| \___/ \___/|_|

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';

/// @notice Interface of Layerswap V8 protocol
interface ILayerswapV8 {
/// @notice Represents an HTLC in the Layerswap V8 protocol
struct HTLC {
uint256 amount; // Locked funds
bytes32 hashlock; // Hash of secret
uint256 secret; // Secret for redemption
address payable sender; // Creator of the HTLC
address payable srcReceiver; // Recipient if conditions met
uint48 timelock; // Refund timestamp
uint8 claimed; // Claimed status (redeemed/refunded)
}

/// @notice Get details of an HTLC by ID
/// @param htlcID Identifier of the HTLC
/// @return HTLC structure details
function getDetails(bytes32 htlcID) external view returns (HTLC memory);
}

contract AtomicPool is ReentrancyGuard {
error PoolNotExists(); // Error: Pool does not exist
error NotFutureTimelock(); // Error: Invalid timelock
error FundsNotSent(); // Error: No funds sent
error NotPassedTimelock(); // Error: Timelock not passed
error InvalidOperation(); // Error: Invalid operation
error NoAllowance(); // Error: Unauthorized access
error NoFunds(); // Error: No funds in pool or already punished
error InvalidSolver(); // Error: Invalid solver
error TransferFailed(); // Error: Fund transfer failed
error HashlockNotMatch(); // Error: Hashlock mismatch

/// @notice Pool locked event
/// @param ID Pool ID
/// @param payer Pool creator
/// @param solver Solver address
/// @param amount Locked amount
/// @param timelock Expiry time
event PoolLocked(uint256 indexed ID, address payer, address solver, uint256 amount, uint48 timelock);

/// @notice Pool unlocked event
/// @param ID Pool ID
/// @param payer Pool creator
/// @param solver Solver address
/// @param amount Unlocked amount
event PoolUnlocked(uint256 indexed ID, address payer, address solver, uint256 amount);

/// @notice Pool timelock extended event
/// @param ID Pool ID
/// @param timelock New timelock
event PoolTimeExtended(uint256 indexed ID, address solver, uint48 timelock);

/// @notice Solver punished event
/// @param ID Pool ID
/// @param amount Penalized amount
/// @param solver Solver address
/// @param punisher Punisher address
event SolverPunished(uint256 indexed ID, address solver, uint256 amount, address punisher);

struct Pool {
uint256 amount; // Locked amount
address payable solver; // Solver address
uint48 timelock; // Timelock expiry
}

uint256 private nonce = 0; // Pool identifier counter
mapping(uint256 => Pool) pools; // Pool storage
mapping(uint256 => address payable) payers; // Payer storage
ILayerswapV8 LayerswapV8; // Layerswap protocol instance

/// @notice Contract constructor
/// @param _LayerswapV8Contract Layerswap V8 contract address
constructor(address _LayerswapV8Contract) {
LayerswapV8 = ILayerswapV8(_LayerswapV8Contract);
}

/// @notice Ensures pool exists
modifier _exists(uint256 ID) {
if (pools[ID].solver == address(0)) revert PoolNotExists();
_;
}

/// @notice Lock a pool with funds
/// @param solver Address of solver
/// @param timelock Expiry time for the pool
/// @return Pool ID
function lockPool(address solver, uint48 timelock) public payable nonReentrant returns (uint256) {
if (msg.value == 0) revert FundsNotSent();
if (timelock < block.timestamp + 86400) revert NotFutureTimelock(); // funds are locked at least for a day
unchecked {
++nonce;
}
pools[nonce] = Pool(msg.value, payable(solver), timelock);
payers[nonce] = payable(msg.sender);
emit PoolLocked(nonce, msg.sender, solver, msg.value, timelock);
return nonce;
}

/// @notice Unlock a pool and transfer funds to payer
/// @param ID Pool ID
/// @return Pool ID
function unlockPool(uint256 ID) public _exists(ID) nonReentrant returns (uint256) {
Pool storage pool = pools[ID];
// This check can be removed because attempting to unlockPool a second time will result in the attacker receiving an amount of 0.
if (pool.amount == 0) revert NoFunds();
if (pool.timelock > block.timestamp) revert NotPassedTimelock();
if (payers[ID] != msg.sender) revert NoAllowance();
pool.amount = 0;
(bool success, ) = payers[ID].call{ value: pool.amount }('');
if (!success) revert TransferFailed();
emit PoolUnlocked(ID, msg.sender, pool.solver, pool.amount);
return ID;
}

/// @notice Extend the timelock of a pool
/// @param ID Pool ID
/// @param timelock New timelock
/// @return Pool ID
function extendPoolTime(uint256 ID, uint48 timelock) public _exists(ID) nonReentrant returns (uint256) {
Pool storage pool = pools[ID];
if (pool.timelock > timelock) revert InvalidOperation();
if (payers[ID] != msg.sender) revert NoAllowance();
pool.timelock = timelock;
emit PoolTimeExtended(ID, pool.solver, timelock);
return ID;
}

/// @notice Punish solver by transferring funds to recipient
/// @param ID Pool ID
/// @param htlcID HTLC ID
/// @param secret Secret to unlock hashlock
/// @return Pool ID
function punishSolver(uint256 ID, bytes32 htlcID, uint256 secret) public _exists(ID) nonReentrant returns (uint256) {
ILayerswapV8.HTLC memory htlc = LayerswapV8.getDetails(htlcID);
Pool storage pool = pools[ID];
if (pool.amount == 0) revert NoFunds();
if (htlc.sender != pool.solver) revert InvalidSolver();
if (htlc.hashlock != sha256(abi.encodePacked(secret))) revert HashlockNotMatch();

// TODO: Implement a check to ensure at least half of the timelock duration has passed to mitigate potential MEV attacks.

pool.amount = 0;
(bool success, ) = htlc.srcReceiver.call{ value: pool.amount }('');
if (!success) revert TransferFailed();
emit SolverPunished(ID, pool.solver, pool.amount, msg.sender);
return ID;
}

/// @notice Get details of a pool
/// @param ID Pool ID
/// @return Pool details
function getPool(uint256 ID) public view returns (Pool memory) {
return pools[ID];
}
}
172 changes: 172 additions & 0 deletions atomicPool/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
ignition: {
strategyConfig: {
create2: {
salt: '0x0000000000000000000000000000000000000000000000000000000000000003',
},
},
},
solidity: {
version: '0.8.23',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
viaIR: true,
},
},
networks: {
mantleSepolia: {
url: 'https://endpoints.omniatech.io/v1/mantle/sepolia/public',
accounts: [process.env.PRIV_KEY],
},
berachain: {
url: 'https://bartio.rpc.berachain.com/',
accounts: [process.env.PRIV_KEY],
},
kakarot_sepolia: {
url: 'https://sepolia-rpc.kakarot.org',
accounts: [process.env.PRIV_KEY],
},
unichainSepolia: {
url: 'https://sepolia.unichain.org',
accounts: [process.env.PRIV_KEY],
},
arbitrumSepolia: {
url: 'https://arbitrum-sepolia.infura.io/v3/2d3e18b5f66f40df8d5df3d990d6d941',
accounts: [process.env.PRIV_KEY],
},
sepolia: {
url: `https://sepolia.infura.io/v3/775081a490784e709d3457ed0e413b21`,
accounts: [process.env.PRIV_KEY],
},
lineaSepolia: {
url: 'https://rpc.sepolia.linea.build',
accounts: [process.env.PRIV_KEY],
chainId: 59141,
},
optimismSepolia: {
url: 'https://sepolia.optimism.io',
accounts: [process.env.PRIV_KEY],
chainId: 11155420,
},
taikoHekla: {
url: 'https://rpc.hekla.taiko.xyz.',
accounts: [process.env.PRIV_KEY],
chainId: 167009,
},
immutableTestnet: {
url: 'https://rpc.testnet.immutable.com',
accounts: [process.env.PRIV_KEY],
chainId: 13473,
},
minato: {
url: 'https://rpc.minato.soneium.org/',
accounts: [process.env.PRIV_KEY],
},
},
etherscan: {
apiKey: {
berachain: process.env.berachain,
unichainSepolia: process.env.unichainSepolia,
immutableTestnet: process.env.immutableTestnet,
optimismSepolia: process.env.optimismSepolia,
lineaSepolia: process.env.lineaSepolia,
taikoHekla: process.env.taikoHekla,
arbitrumSepolia: process.env.arbitrumSepolia,
minato: process.env.minato,
sepolia: process.env.sepolia,
kakarot_sepolia: process.env.kakarotSepolia,
mantleSepolia: process.env.mantleSepolia,
},
customChains: [
{
network: 'mantleSepolia',
chainId: 5003,
urls: {
apiURL: 'https://api-sepolia.mantlescan.xyz/api',
browserURL: 'https://sepolia.mantlescan.xyz/',
},
},
{
network: 'berachain',
chainId: 80084,
urls: {
apiURL: 'https://api.routescan.io/v2/network/testnet/evm/80084/etherscan/api/',
browserURL: 'https://bartio.beratrail.io/',
},
},
{
network: 'unichainSepolia',
chainId: 1301,
urls: {
apiURL: 'https://sepolia.uniscan.xyz/api',
browserURL: ' https://sepolia.uniscan.xyz/',
},
},
{
network: 'lineaSepolia',
chainId: 59141,
urls: {
apiURL: 'https://api-sepolia.lineascan.build/api',
browserURL: 'https://sepolia.lineascan.build',
},
},
{
network: 'optimismSepolia',
chainId: 11155420,
urls: {
apiURL: 'https://api-sepolia-optimistic.etherscan.io/api',
browserURL: 'https://sepolia-optimism.etherscan.io/',
},
},
{
network: 'taikoHekla',
chainId: 167009,
urls: {
apiURL: 'https://blockscoutapi.hekla.taiko.xyz/api',
browserURL: 'https://blockscoutapi.hekla.taiko.xyz/',
},
},
{
network: 'immutableTestnet',
chainId: 13473,
urls: {
apiURL: 'https://explorer.testnet.immutable.com/api',
browserURL: 'https://explorer.testnet.immutable.com/',
},
},
{
network: 'arbitrumSepolia',
chainId: 421614,
urls: {
apiURL: 'https://api-sepolia.arbiscan.io/api',
browserURL: 'https://sepolia.arbiscan.io/',
},
},
{
network: 'kakarot_sepolia',
chainId: 920637907288165,
urls: {
apiURL: 'https://api.routescan.io/v2/network/testnet/evm/920637907288165/etherscan',
browserURL: 'https://sepolia.kakarotscan.org',
},
},
{
network: 'minato',
chainId: 1946,
urls: {
apiURL: 'https://explorer-testnet.soneium.org/api',
browserURL: 'https://explorer-testnet.soneium.org/',
},
},
],
},
sourcify: {
enabled: false,
},
};
6 changes: 6 additions & 0 deletions atomicPool/ignition/modules/atomicPool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("atomicPoolModule", (m) => {
const atomicPool = m.contract('AtomicPool', ['0x01C4036143c56Ff343C4D980b5b6F9D7C86819e1']);
return { atomicPool };
});
Loading
Loading