Skip to content

Commit 740bc31

Browse files
authored
Merge pull request #6 from ampleforth/rescue-funds
2 parents 5d1d380 + f237b64 commit 740bc31

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

contracts/TokenGeyser.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,17 @@ contract TokenGeyser is IStaking, Ownable {
500500
schedule.unlockedShares = schedule.unlockedShares.add(sharesToUnlock);
501501
return sharesToUnlock;
502502
}
503+
504+
/**
505+
* @dev Lets the owner rescue funds air-dropped to the staking pool.
506+
* @param tokenToRescue Address of the token to be rescued.
507+
* @param to Address to which the rescued funds are to be sent.
508+
* @param amount Amount of tokens to be rescued.
509+
* @return Transfer success.
510+
*/
511+
function rescueFundsFromStakingPool(address tokenToRescue, address to, uint256 amount)
512+
public onlyOwner returns (bool) {
513+
514+
return _stakingPool.rescueFunds(tokenToRescue, to, amount);
515+
}
503516
}

contracts/TokenPool.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@ contract TokenPool is Ownable {
2222
function transfer(address to, uint256 value) external onlyOwner returns (bool) {
2323
return token.transfer(to, value);
2424
}
25+
26+
function rescueFunds(address tokenToRescue, address to, uint256 amount) external onlyOwner returns (bool) {
27+
require(address(token) != tokenToRescue, 'TokenPool: Cannot claim token held by the contract');
28+
29+
return IERC20(tokenToRescue).transfer(to, amount);
30+
}
2531
}

contracts/mocks/MockErc20.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
2+
3+
contract MockERC20 is ERC20 {
4+
constructor(uint256 _totalSupply) public {
5+
_mint(msg.sender, _totalSupply);
6+
}
7+
}

test/staking.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
invokeRebase
1111
} = _require('/test/helper');
1212

13+
const MockERC20 = contract.fromArtifact('MockERC20');
1314
const AmpleforthErc20 = contract.fromArtifact('UFragments');
1415
const TokenGeyser = contract.fromArtifact('TokenGeyser');
1516
const InitialSharesPerToken = 10 ** 6;
@@ -218,3 +219,48 @@ describe('staking', function () {
218219
});
219220
});
220221
});
222+
223+
224+
describe('rescueFundsFromStakingPool', function () {
225+
describe('when tokens gets air-dropped', function() {
226+
it('should allow the owner to claim them', async function() {
227+
const accounts = await chain.getUserAccounts();
228+
owner = web3.utils.toChecksumAddress(accounts[0]);
229+
anotherAccount = web3.utils.toChecksumAddress(accounts[8]);
230+
231+
ampl = await AmpleforthErc20.new();
232+
await ampl.initialize(owner);
233+
await ampl.setMonetaryPolicy(owner);
234+
235+
const startBonus = 50;
236+
const bonusPeriod = 86400;
237+
const dist = await TokenGeyser.new(ampl.address, ampl.address, 10, startBonus, bonusPeriod,
238+
InitialSharesPerToken);
239+
240+
await ampl.approve(dist.address, $AMPL(100));
241+
await dist.stake($AMPL(100), []);
242+
243+
const transfers = await ampl.contract.getPastEvents('Transfer');
244+
const transferLog = transfers[transfers.length - 1];
245+
const stakingPool = transferLog.returnValues.to;
246+
247+
expect(await ampl.balanceOf.call(stakingPool)).to.be.bignumber.equal($AMPL(100));
248+
249+
const token = await MockERC20.new(1000);
250+
await token.transfer(stakingPool, 1000);
251+
252+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('0');
253+
await dist.rescueFundsFromStakingPool(
254+
token.address, anotherAccount, 1000
255+
);
256+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('1000');
257+
258+
await expectRevert(
259+
dist.rescueFundsFromStakingPool(ampl.address, anotherAccount, $AMPL(10)),
260+
'TokenPool: Cannot claim token held by the contract'
261+
);
262+
263+
expect(await ampl.balanceOf.call(stakingPool)).to.be.bignumber.equal($AMPL(100));
264+
})
265+
});
266+
});

test/token_pool.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
const { contract, web3 } = require('@openzeppelin/test-environment');
2+
const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers');
3+
const { expect } = require('chai');
4+
5+
const _require = require('app-root-path').require;
6+
const BlockchainCaller = _require('/util/blockchain_caller');
7+
const chain = new BlockchainCaller(web3);
8+
9+
const MockERC20 = contract.fromArtifact('MockERC20');
10+
const TokenPool = contract.fromArtifact('TokenPool');
11+
12+
let token, otherToken, tokenPool, owner, anotherAccount;
13+
describe('tokenPool', function () {
14+
beforeEach('setup contracts', async function () {
15+
const accounts = await chain.getUserAccounts();
16+
owner = web3.utils.toChecksumAddress(accounts[0]);
17+
anotherAccount = web3.utils.toChecksumAddress(accounts[8]);
18+
19+
token = await MockERC20.new(1000);
20+
otherToken = await MockERC20.new(2000);
21+
22+
tokenPool = await TokenPool.new(token.address);
23+
});
24+
25+
describe('balance', function() {
26+
it('should return the balance of the token pool', async function(){
27+
await token.transfer(tokenPool.address, 123);
28+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('123');
29+
await tokenPool.transfer(owner, 99);
30+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('24');
31+
await tokenPool.transfer(owner, 24);
32+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('0');
33+
});
34+
});
35+
36+
describe('transfer', function() {
37+
it('should let the owner transfer funds out', async function(){
38+
await token.transfer(tokenPool.address, 1000);
39+
40+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('1000');
41+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('0');
42+
43+
await tokenPool.transfer(anotherAccount, 1000);
44+
45+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('0');
46+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('1000');
47+
});
48+
49+
it('should NOT let other users transfer funds out', async function(){
50+
await token.transfer(tokenPool.address, 1000);
51+
await expectRevert(
52+
tokenPool.transfer(anotherAccount, 1000, { from: anotherAccount }),
53+
'Ownable: caller is not the owner'
54+
);
55+
});
56+
});
57+
58+
describe('rescueFunds', function() {
59+
beforeEach(async function(){
60+
await token.transfer(tokenPool.address, 1000);
61+
await otherToken.transfer(tokenPool.address, 2000);
62+
63+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('1000');
64+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('0');
65+
expect(await otherToken.balanceOf.call(tokenPool.address)).to.be.bignumber.equal('2000');
66+
expect(await otherToken.balanceOf.call(anotherAccount)).to.be.bignumber.equal('0');
67+
});
68+
69+
it('should let owner users claim excess funds completely', async function(){
70+
await tokenPool.rescueFunds(otherToken.address, anotherAccount, 2000);
71+
72+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('1000');
73+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('0');
74+
expect(await otherToken.balanceOf.call(tokenPool.address)).to.be.bignumber.equal('0');
75+
expect(await otherToken.balanceOf.call(anotherAccount)).to.be.bignumber.equal('2000');
76+
});
77+
78+
it('should let owner users claim excess funds partially', async function(){
79+
await tokenPool.rescueFunds(otherToken.address, anotherAccount, 777);
80+
81+
expect(await tokenPool.balance.call()).to.be.bignumber.equal('1000');
82+
expect(await token.balanceOf.call(anotherAccount)).to.be.bignumber.equal('0');
83+
expect(await otherToken.balanceOf.call(tokenPool.address)).to.be.bignumber.equal('1223');
84+
expect(await otherToken.balanceOf.call(anotherAccount)).to.be.bignumber.equal('777');
85+
});
86+
87+
it('should NOT let owner claim more than available excess funds', async function(){
88+
await expectRevert(
89+
tokenPool.rescueFunds(otherToken.address, anotherAccount, 2001),
90+
'ERC20: transfer amount exceeds balance'
91+
);
92+
});
93+
94+
it('should NOT let owner users claim held funds', async function(){
95+
await expectRevert(
96+
tokenPool.rescueFunds(token.address, anotherAccount, 1000),
97+
'TokenPool: Cannot claim token held by the contract'
98+
);
99+
});
100+
101+
it('should NOT let other users users claim excess funds', async function(){
102+
await expectRevert(
103+
tokenPool.rescueFunds(otherToken.address, anotherAccount, 2000, { from: anotherAccount }),
104+
'Ownable: caller is not the owner'
105+
);
106+
});
107+
});
108+
});

0 commit comments

Comments
 (0)