Skip to content

Commit f233c6f

Browse files
authored
Merge pull request #249 from pooltogether/pool-1824-add-deposittoanddelegate-to
feat(EIP2612): add permitAndDepositToAndDelegate
2 parents 3092ea0 + 6fdac95 commit f233c6f

4 files changed

Lines changed: 146 additions & 71 deletions

File tree

contracts/permit/EIP2612PermitAndDeposit.sol

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,81 @@ import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
77
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
88

99
import "../interfaces/IPrizePool.sol";
10+
import "../interfaces/ITicket.sol";
11+
12+
/**
13+
* @notice Secp256k1 signature values.
14+
* @param v `v` portion of the signature
15+
* @param r `r` portion of the signature
16+
* @param s `s` portion of the signature
17+
*/
18+
struct Signature {
19+
uint8 v;
20+
bytes32 r;
21+
bytes32 s;
22+
}
1023

1124
/// @title Allows users to approve and deposit EIP-2612 compatible tokens into a prize pool in a single transaction.
1225
contract EIP2612PermitAndDeposit {
1326
using SafeERC20 for IERC20;
1427

1528
/**
1629
* @notice Permits this contract to spend on a user's behalf, and deposits into the prize pool.
30+
* @custom:experimental This function has not been audited yet.
1731
* @dev The `spender` address required by the permit function is the address of this contract.
18-
* @param _token Address of the EIP-2612 token to approve and deposit.
19-
* @param _owner Token owner's address (Authorizer).
20-
* @param _amount Amount of tokens to deposit.
21-
* @param _deadline Timestamp at which the signature expires.
22-
* @param _v `v` portion of the signature.
23-
* @param _r `r` portion of the signature.
24-
* @param _s `s` portion of the signature.
25-
* @param _prizePool Address of the prize pool to deposit into.
26-
* @param _to Address that will receive the tickets.
32+
* @param _owner Token owner's address (Authorizer)
33+
* @param _amount Amount of tokens to deposit
34+
* @param _deadline Timestamp at which the signature expires
35+
* @param _permitSignature Permit signature
36+
* @param _delegateSignature Delegate signature
37+
* @param _prizePool Address of the prize pool to deposit into
38+
* @param _to Address that will receive the tickets
39+
* @param _delegate The address to delegate the prize pool tickets to
2740
*/
28-
function permitAndDepositTo(
29-
address _token,
41+
function permitAndDepositToAndDelegate(
3042
address _owner,
3143
uint256 _amount,
3244
uint256 _deadline,
33-
uint8 _v,
34-
bytes32 _r,
35-
bytes32 _s,
36-
address _prizePool,
37-
address _to
45+
Signature calldata _permitSignature,
46+
Signature calldata _delegateSignature,
47+
IPrizePool _prizePool,
48+
address _to,
49+
address _delegate
3850
) external {
3951
require(msg.sender == _owner, "EIP2612PermitAndDeposit/only-signer");
4052

41-
IERC20Permit(_token).permit(_owner, address(this), _amount, _deadline, _v, _r, _s);
53+
ITicket _ticket = _prizePool.getTicket();
54+
address _token = _prizePool.getToken();
55+
56+
IERC20Permit(_token).permit(
57+
_owner,
58+
address(this),
59+
_amount,
60+
_deadline,
61+
_permitSignature.v,
62+
_permitSignature.r,
63+
_permitSignature.s
64+
);
65+
66+
_depositTo(_token, _owner, _amount, address(_prizePool), _to);
4267

43-
_depositTo(_token, _owner, _amount, _prizePool, _to);
68+
_ticket.delegateWithSignature(
69+
_owner,
70+
_delegate,
71+
_deadline,
72+
_delegateSignature.v,
73+
_delegateSignature.r,
74+
_delegateSignature.s
75+
);
4476
}
4577

4678
/**
4779
* @notice Deposits user's token into the prize pool.
48-
* @param _token Address of the EIP-2612 token to approve and deposit.
49-
* @param _owner Token owner's address (Authorizer).
50-
* @param _amount Amount of tokens to deposit.
51-
* @param _prizePool Address of the prize pool to deposit into.
52-
* @param _to Address that will receive the tickets.
80+
* @param _token Address of the EIP-2612 token to approve and deposit
81+
* @param _owner Token owner's address (Authorizer)
82+
* @param _amount Amount of tokens to deposit
83+
* @param _prizePool Address of the prize pool to deposit into
84+
* @param _to Address that will receive the tickets
5385
*/
5486
function _depositTo(
5587
address _token,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pooltogether/v4-core",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "PoolTogether V4 Core Smart Contracts",
55
"main": "index.js",
66
"license": "GPL-3.0",

test/Ticket.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Signer } from '@ethersproject/abstract-signer';
21
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
32
import { expect } from 'chai';
4-
import { deployMockContract, MockContract } from 'ethereum-waffle';
53
import { utils, Contract, ContractFactory, BigNumber } from 'ethers';
6-
import hre, { ethers } from 'hardhat';
4+
import { ethers } from 'hardhat';
75
import { delegateSignature } from './helpers/delegateSignature';
86
import { increaseTime as increaseTimeHelper } from './helpers/increaseTime';
97

@@ -12,7 +10,7 @@ const newDebug = require('debug');
1210
const debug = newDebug('pt:Ticket.test.ts');
1311

1412
const { constants, getSigners, provider } = ethers;
15-
const { AddressZero, MaxUint256 } = constants;
13+
const { AddressZero } = constants;
1614
const { getBlock } = provider;
1715
const { parseEther: toWei } = utils;
1816

@@ -928,8 +926,7 @@ describe('Ticket', () => {
928926

929927
describe('delegateWithSignature()', () => {
930928
it('should allow somone to delegate with a signature', async () => {
931-
// @ts-ignore
932-
const { user, delegate, nonce, deadline, v, r, s } = await delegateSignature({
929+
const { user, delegate, deadline, v, r, s } = await delegateSignature({
933930
ticket,
934931
userWallet: wallet1,
935932
delegate: wallet2.address,

test/permit/EIP2612PermitAndDeposit.test.ts

Lines changed: 87 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,175 @@
11
import { Signer } from '@ethersproject/abstract-signer';
2+
import { SignatureLike } from '@ethersproject/bytes';
23
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
34
import { expect } from 'chai';
4-
import { utils, Contract } from 'ethers';
5+
import { utils, Contract, ContractFactory } from 'ethers';
56
import { deployMockContract, MockContract } from 'ethereum-waffle';
67
import hre, { ethers } from 'hardhat';
78

9+
import { delegateSignature } from '../helpers/delegateSignature';
810
import { signPermit } from '../helpers/signPermit';
911

10-
const { getContractFactory, getSigners, provider } = ethers;
12+
const { constants, getContractFactory, getSigners, provider } = ethers;
13+
const { AddressZero } = constants;
1114
const { artifacts } = hre;
1215
const { getNetwork } = provider;
1316
const { parseEther: toWei, splitSignature } = utils;
1417

1518
describe('EIP2612PermitAndDeposit', () => {
1619
let wallet: SignerWithAddress;
1720
let wallet2: SignerWithAddress;
18-
let wallet3: SignerWithAddress;
21+
let prizeStrategyManager: SignerWithAddress;
1922

2023
let permitAndDeposit: Contract;
2124
let usdc: Contract;
22-
let prizePool: MockContract;
25+
let PrizePoolHarness: ContractFactory;
26+
let prizePool: Contract;
27+
let ticket: Contract;
28+
let yieldSourceStub: MockContract;
2329

2430
let chainId: number;
2531

26-
type EIP2612PermitAndDepositTo = {
32+
type EIP2612PermitAndDepositToAndDelegate = {
2733
prizePool: string;
2834
fromWallet?: SignerWithAddress;
2935
to: string;
3036
amount: string;
37+
delegateAddress: string;
3138
};
3239

33-
async function permitAndDepositTo({
40+
async function permitAndDepositToAndDelegate({
3441
prizePool,
3542
fromWallet,
3643
to,
3744
amount,
38-
}: EIP2612PermitAndDepositTo) {
45+
delegateAddress,
46+
}: EIP2612PermitAndDepositToAndDelegate) {
3947
if (!fromWallet) {
4048
fromWallet = wallet;
4149
}
4250

43-
const deadline = new Date().getTime();
51+
const { user, delegate, deadline, v, r, s } = await delegateSignature({
52+
ticket,
53+
userWallet: fromWallet,
54+
delegate: delegateAddress,
55+
});
56+
57+
const delegateSign: SignatureLike = { v, r, s };
4458

45-
let permit = await signPermit(
46-
wallet,
59+
const permit = await signPermit(
60+
fromWallet,
4761
{
4862
name: 'USD Coin',
4963
version: '1',
5064
chainId,
5165
verifyingContract: usdc.address,
5266
},
5367
{
54-
owner: wallet.address,
68+
owner: user,
5569
spender: permitAndDeposit.address,
5670
value: amount,
5771
nonce: 0,
5872
deadline,
5973
},
6074
);
6175

62-
let { v, r, s } = splitSignature(permit.sig);
63-
64-
return permitAndDeposit
65-
.connect(fromWallet)
66-
.permitAndDepositTo(
67-
usdc.address,
68-
wallet.address,
69-
amount,
70-
deadline,
71-
v,
72-
r,
73-
s,
74-
prizePool,
75-
to,
76-
);
76+
const permitSignature = splitSignature(permit.sig);
77+
78+
return permitAndDeposit.permitAndDepositToAndDelegate(
79+
user,
80+
amount,
81+
deadline,
82+
permitSignature,
83+
delegateSign,
84+
prizePool,
85+
to,
86+
delegate,
87+
);
7788
}
7889

7990
beforeEach(async () => {
80-
[wallet, wallet2, wallet3] = await getSigners();
91+
[wallet, wallet2, prizeStrategyManager] = await getSigners();
8192

8293
const network = await getNetwork();
8394
chainId = network.chainId;
8495

8596
const Usdc = await getContractFactory('EIP2612PermitMintable');
8697
usdc = await Usdc.deploy('USD Coin', 'USDC');
8798

88-
const IPrizePool = await artifacts.readArtifact('IPrizePool');
89-
prizePool = await deployMockContract(wallet as Signer, IPrizePool.abi);
99+
const YieldSourceStub = await artifacts.readArtifact('YieldSourceStub');
100+
yieldSourceStub = await deployMockContract(wallet as Signer, YieldSourceStub.abi);
101+
await yieldSourceStub.mock.depositToken.returns(usdc.address);
90102

91-
const EIP2612PermitAndDeposit = await getContractFactory('EIP2612PermitAndDeposit');
103+
PrizePoolHarness = await getContractFactory('PrizePoolHarness', wallet);
104+
prizePool = await PrizePoolHarness.deploy(wallet.address, yieldSourceStub.address);
92105

106+
const EIP2612PermitAndDeposit = await getContractFactory('EIP2612PermitAndDeposit');
93107
permitAndDeposit = await EIP2612PermitAndDeposit.deploy();
108+
109+
const Ticket = await getContractFactory('TicketHarness');
110+
ticket = await Ticket.deploy('PoolTogether Usdc Ticket', 'PcUSDC', 18, prizePool.address);
111+
112+
await prizePool.setTicket(ticket.address);
113+
await prizePool.setPrizeStrategy(prizeStrategyManager.address);
94114
});
95115

96-
describe('permitAndDepositTo()', () => {
97-
it('should work', async () => {
116+
describe('permitAndDepositToAndDelegate()', () => {
117+
it('should deposit and delegate to itself', async () => {
118+
const amount = toWei('100');
119+
98120
await usdc.mint(wallet.address, toWei('1000'));
99121

100-
await prizePool.mock.depositTo.withArgs(wallet2.address, toWei('100')).returns();
122+
await yieldSourceStub.mock.supplyTokenTo.withArgs(amount, prizePool.address).returns();
101123

102-
await permitAndDepositTo({
124+
await permitAndDepositToAndDelegate({
103125
prizePool: prizePool.address,
104-
to: wallet2.address,
126+
to: wallet.address,
105127
amount: '100000000000000000000',
128+
delegateAddress: wallet.address,
106129
});
107130

108-
expect(await usdc.allowance(permitAndDeposit.address, prizePool.address)).to.equal(
109-
toWei('100'),
110-
);
131+
expect(await usdc.balanceOf(prizePool.address)).to.equal(amount);
132+
expect(await usdc.balanceOf(wallet.address)).to.equal(toWei('900'));
133+
expect(await ticket.balanceOf(wallet.address)).to.equal(amount);
134+
expect(await ticket.delegateOf(wallet.address)).to.equal(wallet.address);
135+
});
136+
137+
it('should deposit and delegate to someone else', async () => {
138+
const amount = toWei('100');
139+
140+
await usdc.mint(wallet.address, toWei('1000'));
141+
142+
await yieldSourceStub.mock.supplyTokenTo.withArgs(amount, prizePool.address).returns();
111143

112-
expect(await usdc.balanceOf(permitAndDeposit.address)).to.equal(toWei('100'));
144+
await permitAndDepositToAndDelegate({
145+
prizePool: prizePool.address,
146+
to: wallet.address,
147+
amount: '100000000000000000000',
148+
delegateAddress: wallet2.address,
149+
});
150+
151+
expect(await usdc.balanceOf(prizePool.address)).to.equal(amount);
113152
expect(await usdc.balanceOf(wallet.address)).to.equal(toWei('900'));
153+
expect(await ticket.balanceOf(wallet.address)).to.equal(amount);
154+
expect(await ticket.balanceOf(wallet2.address)).to.equal(toWei('0'));
155+
expect(await ticket.delegateOf(wallet.address)).to.equal(wallet2.address);
156+
expect(await ticket.delegateOf(wallet2.address)).to.equal(AddressZero);
114157
});
115158

116159
it('should not allow anyone else to use the signature', async () => {
160+
const amount = toWei('100');
161+
117162
await usdc.mint(wallet.address, toWei('1000'));
118163

119-
await prizePool.mock.depositTo.withArgs(wallet2.address, toWei('100')).returns();
164+
await yieldSourceStub.mock.supplyTokenTo.withArgs(amount, prizePool.address).returns();
120165

121166
await expect(
122-
permitAndDepositTo({
167+
permitAndDepositToAndDelegate({
123168
prizePool: prizePool.address,
124169
to: wallet2.address,
125170
fromWallet: wallet2,
126171
amount: '100000000000000000000',
172+
delegateAddress: wallet2.address,
127173
}),
128174
).to.be.revertedWith('EIP2612PermitAndDeposit/only-signer');
129175
});

0 commit comments

Comments
 (0)