This code was made as a challenge for a position as a solidity developer.
The challenge was to create a token swapping smart contract. You have two tokens. You can swap each of it for another one. Rates was one-to-one. Basically the behaviour was :
- I got Token1, I will swap for TokenZ
- I got Token2, I will swap for TokenZ
- I got TokenZ, I can swap for either Token1 or Token2
There was some requirements for the task :
- A wrapper contract with two methods name was provided :
function swap(address token_, uint amount)
andfunction unswap(address token_, uint amount)
- Token1 should be minted outside the wrapper contract
- Token2 should be minted outside the wrapper contract
- TokenZ should be minted inside the wrapper contract
- External code (like OpenZeppelin) was allowed
- Describe the call sequence for everything to do
OK, this should not be difficult. Create 3 tokens, just inheriting an OpenZeppelin code. What could go wrong ?
Just three contract Token is ERC20PresetMinterPauser
. I choose ERC20PresetMinterPauser
class because I need to be able to mint the tokens
So, in this wrapper I should implement the two functions swap()
and unswap()
What swap()
do ?
- Verify that the provided token address is either Token1 or Token2
- Transfer the requested amount of Token 1 (or Token2) from the sender to the contract
- Mint the same amount of TokenZ to the sender address
- emit the swap event
And what unswap()
do ?
- Verify that the provided token address is either Token1 or Token2
- Burn the amount of TokenZ from the sender address
- Transfer the amount of Token 1 (or Token 2) from the contract to the sender
- emit the unswap event
Remarks
Questions were raised.
First, should I add the approve call before the transferFrom
call ?
I think that it is not necessary. The approve should be made outside the (un)swap call. (un)swap functions must be independent of the available allowance
Second, should I verify the allowance before the transferFrom
call ?
Same as first. The transferFrom already check for allowance and its require
will fail if token amount is missing
Third, should I verify the balance before the transfer
in the unswap call ?
I think I have to, and so I missed this one. I have too much trust about other code, and it is a really big mistake in the crypto-game.
Finally, I missed a require(amount > 0, "Amount is zero);
request at both functions. I leave the controls to the subcall, avoiding to write the same check.
It is bad habit, I know...
Here are all the sequences you will find in the test sections to execute a fully functional workflow of the challenge.
The amounts used in this sequence are toWei(100, 'eth')
and toWei(50, 'eth)
- Deploy Token1()
- Deploy Token2()
- Deploy TokenZ()
- Deploy Wrapper(Token1.address, Token2.address, TokenZ.address)
- Call TokenZ.grantRole(TokenZ.MINTER_ROLE, Wrapper.address)
- Call TokenZ.grantRole(TokenZ.DEFAULT_ADMIN_ROLE, Wrapper.address)
- Call TokenZ.revokeRole(TokenZ.MINTER_ROLE, "your wallet address")
- Call TokenZ.revokeRole(TokenZ.DEFAULT_ADMIN_ROLE, "your wallet address")
- Call Token1.mint("your wallet address", "100000000000000000000") // 100 eth
- Call Token2.mint("your wallet address", "100000000000000000000") // 100 eth
- Call Token2.mint(Wrapper.address, "50000000000000000000") // 50 eth
- Call Token1.approve(Wrapper.address, "100000000000000000000") // 100 eth
- Call Wrapper.swap(Token1.address, "100000000000000000000") // 100 eth
- Call TokenZ.approve(Wrapper.address, "50000000000000000000") // 50 eth
- Call Wrapper.unswap(Token2.address, "100000000000000000000") // 100 eth
PS : be careful to GRANT ROLE before to REVOKE ROLE