My goal is to create a simple Ethereum smart contract that allows for an atomic swap between tokens. Initially it should've been a cross-chain swap but I am struggling to do the basics. I am using ERC20 tokens that i have created using the openzeppelin ERC20.sol and IERC20.sol contracts and using Remix-IDE to create the contracts.
When i try to transfer the tokens or use the claim()
function, I encounter errors such as
' ERC20: transfer amount exceeds balance'
' ERC20: insufficient allowance'
'Allowance must be greater than 0'
These errors occur even after i use the approve()
function in the AliceCoin and BobCoin contracts
Some Questions:
A: Do i need to deploy the tokens separately before i can use it in my contract?
B: Should all the ERC20 functions be in the same contract as my atomic swap contract?
C: I have create ETH addresses for owner, recipient and token address. But how to i utilize the Remix IDE addresses given to me in my contract?
Any help is much appreciated
Code:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.1;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract AliceCoin is ERC20 {
constructor(uint256 supply) ERC20("AliceCoin", "ALI") {
_mint(msg.sender, supply);
}
}
contract BobCoin is ERC20{
constructor(uint256 supply) ERC20("BobCoin", "BOB"){
_mint(msg.sender, supply);
}
}
contract AtomicSwap{
ERC20 public tokenA;
ERC20 public tokenB;
// A constructor for the smart contract
constructor() payable {
//_owner = payable(msg.sender);
//_recipient = payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
tokenA = new AliceCoin(100000);
tokenB = new BobCoin(100000);
}
/*
Attributes of the Atomic Swap
-- Timelock
-- Hashlock
-- Address transfer from
-- Address transfer to
-- secret key
-- Token | That would use functions from the interface
--
*/
struct Swap {
// User who should recieve the contract
address payable recipient;
// Owner of the coin
address payable Owner;
// Address of the token
address tokenAddress;
// The amount of the tokens swapped
uint256 amount;
//uint256 amount; // An amount of the token wanting to be transferred
// Time stated for Swap to be executed
uint256 timelock;
// Cryptographic secret key
bytes32 Hashlock; //0xd218ef7a2461c961fdd5c0cd5a547f52b863d14db3e08e55f365e4cd0b4333c5;
// Secret key
string secret;
// Boolean to check if the owner has been refunded
bool refunded;
// Boolean to check if the token has been claimed
bool claimed;
}
mapping(address => mapping(address => uint256)) private _allowances;
mapping(bytes32 => Swap) public swaps;
event NewAtomicSwap(
bytes32 swapId,
address payable Owner,
address payable recipient,
address tokenAddress,
uint256 amount,
bytes32 Hashlock,
uint256 timelock
);
event Claimed(
bytes32 swapId
);
event Refunded(
bytes32 swapId
);
// Modifiers
modifier checkAllowance(address _token, address _Owner, uint256 _amount){
require(_amount > 0, "Token amount must be greater than 0");
require(
ERC20(_token).allowance(_Owner, address(this)) >= _amount,
"Allowance must be greater than 0"
);
_;
}
modifier futureTimelock(uint256 _time){
require(_time > block.timestamp, "timelock has to be set in the future");
_;
}
modifier claimable(bytes32 _swapId) {
require(swaps[_swapId].recipient == msg.sender, "This is not the right recipient");
require(swaps[_swapId].claimed == false, "already claimed");
require(swaps[_swapId].refunded == false, "already refunded");
_;
}
modifier matchingHashlocks(bytes32 _swapId, bytes32 _x){
require(
swaps[_swapId].Hashlock == keccak256(abi.encodePacked(_x)),
"incorrect hashlock"
);
_;
}
modifier existingContract(bytes32 _swapId) {
require(haveContract(_swapId), "contract does not exist");
_;
}
modifier refundable(bytes32 _swapId) {
require(swaps[_swapId].Owner == msg.sender, "Only the sender of this coin can refund");
require(swaps[_swapId].refunded == false, "Already refunded");
require(swaps[_swapId].claimed == false, "Already claimed");
require(swaps[_swapId].timelock <= block.timestamp, "Timelock not yet passed");
_;
}
function newSwap(
address payable _recipient,
bytes32 _Hashlock,
uint256 _timelock,
address _tokenAddress,
uint256 _amount
)
public // Visibility
payable
checkAllowance(_tokenAddress, msg.sender, _amount)
futureTimelock(_timelock)
returns(bytes32 swapId)
{
swapId = keccak256(
abi.encodePacked(
msg.sender,
_recipient,
_tokenAddress,
_amount,
_Hashlock,
_timelock
)
);
if(haveContract(swapId))
revert("Contract exists");
if(!AliceCoin(_tokenAddress).transfer(_recipient , _amount))
revert("transfer failed");
swaps[swapId] = Swap({
recipient : _recipient,
Owner : payable(msg.sender),
tokenAddress : _tokenAddress,
amount : msg.value,
timelock : getTimestamp() + 60000,
Hashlock : _Hashlock,
secret : "djkcoeuxhjkdf",
Open : false,
locked : false,
finished : false,
refunded : false,
claimed: false
});
emit NewAtomicSwap(
swapId,
payable(msg.sender),
_recipient,
_tokenAddress,
_amount,
_Hashlock,
_timelock
);
}
/* Function for recipient to claim token */
/* Only be claimed if Owner has opened the swap with _____ */
function claim(bytes32 _swapId, bytes32 _Hashlock)
public
payable
claimable(_swapId)
matchingHashlocks(_swapId, _Hashlock)
existingContract(_swapId)
returns (bool)
{
Swap storage s = swaps[_swapId];
s.Hashlock = _Hashlock;
s.claimed = true;
AliceCoin(s.tokenAddress).transfer(s.recipient, s.amount);
emit Claimed(_swapId);
return true;
}
function refund(bytes32 _swapId)
external
existingContract(_swapId)
refundable(_swapId)
returns (bool)
{
Swap storage s = swaps[_swapId];
s.refunded = true;
AliceCoin(s.tokenAddress).transfer(s.Owner, s.amount);
emit Refunded(_swapId);
return true;
}
function haveContract(bytes32 _swapId)
internal
view
returns (bool available)
{
available = (swaps[_swapId].Owner != address(0));
}
In your smart contract the issue is that the owner about your two ERC20 token (AliceCoin and BobCoin) is your smart contact. When you do this:
The
msg.sender
that interact with your ERC20 two contracts is smart contact itself. In this way, AtomicSwap smart contract has the ERC20 tokens and it is the owner and not your address! For resolve this issue, you must change your constructor logic in this way:And you must to deploy first ERC20 smart contracts and then your
AtomicSwap
contract, and pass into his constructor the address about ERC20 smart contracts. Another issue is the use abouttransfer
innewSwap()
function. This method to move tokens don't include two transactions like: approve+transfer consequently here you throw the error 'Allowance must be greater than 0'. To solve this issue you must to change this line:with this:
that allow you to transfer user token into smart contract. I made some changes to your original smart contract, you can see and test this code: