Skip to content

Commit

Permalink
Wrap and deposit tokens in a single transactions (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev authored Dec 13, 2021
1 parent 591e38f commit c689612
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 19 deletions.
20 changes: 16 additions & 4 deletions contracts/SBCWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
}
8 changes: 6 additions & 2 deletions contracts/SBCWrapperProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
9 changes: 5 additions & 4 deletions migrations/1_deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ 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
await token.setMinter(wrapper.address)
if (accounts[0].toLowerCase() !== admin.toLowerCase()) {
await tokenProxy.setAdmin(admin)
}

// deploy deposit contract
await deployer.deploy(SBCDepositContractProxy, admin, token.address)
}
}
26 changes: 20 additions & 6 deletions test/deposit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion test/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions test/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit c689612

Please sign in to comment.