From c6896124140bf737a75a365cd51a4caa8ecb6973 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Mon, 13 Dec 2021 15:03:04 +0400 Subject: [PATCH] Wrap and deposit tokens in a single transactions (#14) --- contracts/SBCWrapper.sol | 20 ++++++++++++++++---- contracts/SBCWrapperProxy.sol | 8 ++++++-- migrations/1_deploy.js | 9 +++++---- test/deposit.js | 26 ++++++++++++++++++++------ test/token.js | 2 +- test/wrapper.js | 4 ++-- 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/contracts/SBCWrapper.sol b/contracts/SBCWrapper.sol index 424e0a4..66f44dd 100644 --- a/contracts/SBCWrapper.sol +++ b/contracts/SBCWrapper.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "./utils/PausableEIP1967Admin.sol"; import "./SBCToken.sol"; +import "./SBCDepositContract.sol"; /** * @title SBCWrapper @@ -26,14 +27,16 @@ contract SBCWrapper is IERC677Receiver, PausableEIP1967Admin, Claimable, Reentra mapping(address => uint256) public tokenRate; SBCToken public immutable sbcToken; + SBCDepositContract public immutable sbcDepositContract; event Swap(address indexed token, address indexed user, uint256 amount, uint256 received); event SwapRateUpdated(address indexed token, uint256 rate); event TokenSwapEnabled(address indexed token); event TokenSwapPaused(address indexed token); - constructor(SBCToken _sbcToken) { + constructor(SBCToken _sbcToken, SBCDepositContract _depositContract) { sbcToken = _sbcToken; + sbcDepositContract = _depositContract; } /** @@ -108,16 +111,23 @@ contract SBCWrapper is IERC677Receiver, PausableEIP1967Admin, Claimable, Reentra * @dev ERC677 callback for swapping tokens in the simpler way during transferAndCall. * @param from address of the received token contract. * @param value amount of the received tokens. + * @param data should be empty for a simple token swap, otherwise will pass it further to the deposit contract. */ function onTokenTransfer( address from, uint256 value, - bytes calldata + bytes calldata data ) external override nonReentrant whenNotPaused returns (bool) { address token = _msgSender(); require(tokenStatus[token] == TokenStatus.ENABLED, "SBCWrapper: token is not enabled"); - _swapTokens(from, token, value); + if (data.length == 0) { + _swapTokens(from, token, value); + } else { + uint256 swappedAmount = _swapTokens(address(this), token, value); + sbcToken.transferAndCall(address(sbcDepositContract), swappedAmount, data); + } + return true; } @@ -139,12 +149,14 @@ contract SBCWrapper is IERC677Receiver, PausableEIP1967Admin, Claimable, Reentra address _receiver, address _token, uint256 _amount - ) internal { + ) internal returns (uint256) { uint256 acquired = (_amount * tokenRate[_token]) / 1 ether; require(acquired > 0, "SBCWrapper: invalid amount"); sbcToken.mint(_receiver, acquired); emit Swap(_token, _receiver, _amount, acquired); + + return acquired; } } diff --git a/contracts/SBCWrapperProxy.sol b/contracts/SBCWrapperProxy.sol index 1ef5f13..8578b65 100644 --- a/contracts/SBCWrapperProxy.sol +++ b/contracts/SBCWrapperProxy.sol @@ -10,8 +10,12 @@ import "./SBCWrapper.sol"; * @dev Upgradeable version of the underlying SBCWrapper. */ contract SBCWrapperProxy is EIP1967Proxy { - constructor(address _admin, SBCToken _token) { + constructor( + address _admin, + SBCToken _token, + SBCDepositContract _depositContract + ) { _setAdmin(_admin); - _setImplementation(address(new SBCWrapper(_token))); + _setImplementation(address(new SBCWrapper(_token, _depositContract))); } } diff --git a/migrations/1_deploy.js b/migrations/1_deploy.js index bee1a29..803f96c 100644 --- a/migrations/1_deploy.js +++ b/migrations/1_deploy.js @@ -17,8 +17,12 @@ module.exports = async function (deployer, network, accounts) { const tokenProxy = await SBCTokenProxy.deployed() const token = await SBCToken.at(tokenProxy.address) + // deploy deposit contract + await deployer.deploy(SBCDepositContractProxy, admin, token.address) + const depositContractProxy = await SBCDepositContractProxy.deployed() + // deploy token wrapper - await deployer.deploy(SBCWrapperProxy, admin, token.address) + await deployer.deploy(SBCWrapperProxy, admin, token.address, depositContractProxy.address) const wrapper = await SBCWrapperProxy.deployed() // set token minter to deployed wrapper @@ -26,8 +30,5 @@ module.exports = async function (deployer, network, accounts) { if (accounts[0].toLowerCase() !== admin.toLowerCase()) { await tokenProxy.setAdmin(admin) } - - // deploy deposit contract - await deployer.deploy(SBCDepositContractProxy, admin, token.address) } } diff --git a/test/deposit.js b/test/deposit.js index 6513239..4dd7808 100644 --- a/test/deposit.js +++ b/test/deposit.js @@ -57,15 +57,15 @@ contract('SBCDepositContractProxy', (accounts) => { stake = await IERC677.new() tokenProxy = await SBCTokenProxy.new(accounts[0], 'SBC Token', 'SBCT') token = await SBCToken.at(tokenProxy.address) - wrapperProxy = await SBCWrapperProxy.new(accounts[0], token.address) - wrapper = await SBCWrapper.at(wrapperProxy.address) - await token.setMinter(wrapper.address) contractProxy = await SBCDepositContractProxy.new(accounts[0], token.address) contract = await SBCDepositContract.at(contractProxy.address) + wrapperProxy = await SBCWrapperProxy.new(accounts[0], token.address, contract.address) + wrapper = await SBCWrapper.at(wrapperProxy.address) + await token.setMinter(wrapper.address) - await wrapper.enableToken(stake.address, web3.utils.toWei('1')) - await stake.transferAndCall(wrapper.address, deposit.value + '00', '0x') - expect((await token.balanceOf(accounts[0])).toString()).to.be.equal('3200000000000000000000') + await wrapper.enableToken(stake.address, web3.utils.toWei('32')) + await stake.transferAndCall(wrapper.address, web3.utils.toWei('100'), '0x') + expect((await token.balanceOf(accounts[0])).toString()).to.be.equal(web3.utils.toWei('3200')) }) it('should deposit', async () => { @@ -191,6 +191,20 @@ contract('SBCDepositContractProxy', (accounts) => { } }) + it('should wrap and deposit in a single transaction', async () => { + const invalidData = joinHex([deposit.withdrawal_credentials, deposit.pubkey, deposit.signature, invalidDataRoot]) + const data = joinHex([deposit.withdrawal_credentials, deposit.pubkey, deposit.signature, deposit.deposit_data_root]) + + expect(await contract.get_deposit_count()).to.be.equal('0x0000000000000000') + await stake.transferAndCall(wrapper.address, web3.utils.toWei('1'), invalidData).should.be.rejected + await stake.transferAndCall(wrapper.address, web3.utils.toWei('32'), data).should.be.rejected + await stake.transferAndCall(wrapper.address, web3.utils.toWei('1'), data) + + expect(await contract.get_deposit_count()).to.be.equal('0x0100000000000000') + expect(await contract.get_deposit_root()).to.be.equal('0x4e84f51e6b1cf47fd51d021635d791b9c99fe915990061a5a10390b9140e3592') + expect((await token.balanceOf(contract.address)).toString()).to.be.equal('32000000000000000000') + }) + it('should claim tokens', async () => { const otherToken = await IERC677.new() await token.transfer(contract.address, 1) diff --git a/test/token.js b/test/token.js index 0a86210..f1f5e0e 100644 --- a/test/token.js +++ b/test/token.js @@ -21,7 +21,7 @@ contract('SBCTokenProxy', (accounts) => { stake = await IERC677.new() tokenProxy = await SBCTokenProxy.new(accounts[0], 'SBC Token', 'SBCT') token = await SBCToken.at(tokenProxy.address) - wrapperProxy = await SBCWrapperProxy.new(accounts[0], token.address) + wrapperProxy = await SBCWrapperProxy.new(accounts[0], token.address, accounts[1]) wrapper = await SBCWrapper.at(wrapperProxy.address) await token.setMinter(wrapper.address) diff --git a/test/wrapper.js b/test/wrapper.js index 3c4dd87..2bbb3d5 100644 --- a/test/wrapper.js +++ b/test/wrapper.js @@ -26,7 +26,7 @@ contract('SBCWrapperProxy', (accounts) => { stake = await IERC677.new() tokenProxy = await SBCTokenProxy.new(accounts[0], 'SBC Token', 'SBCT') token = await SBCToken.at(tokenProxy.address) - wrapperProxy = await SBCWrapperProxy.new(accounts[0], token.address) + wrapperProxy = await SBCWrapperProxy.new(accounts[0], token.address, accounts[1]) wrapper = await SBCWrapper.at(wrapperProxy.address) await token.setMinter(wrapper.address) @@ -161,7 +161,7 @@ contract('SBCWrapperProxy', (accounts) => { }) it('should upgrade', async () => { - const impl = await SBCWrapper.new(token.address) + const impl = await SBCWrapper.new(token.address, accounts[1]) await wrapperProxy.upgradeTo(impl.address, { from: accounts[1] }).should.be.rejected await wrapperProxy.upgradeTo(impl.address, { from: accounts[0] }) expect(await wrapperProxy.implementation()).to.be.equal(impl.address)