Skip to content

Commit

Permalink
Merge 78e8b2e into f233c6f
Browse files Browse the repository at this point in the history
  • Loading branch information
PierrickGT authored Nov 8, 2021
2 parents f233c6f + 78e8b2e commit 8139eeb
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 41 deletions.
109 changes: 84 additions & 25 deletions contracts/permit/EIP2612PermitAndDeposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,67 +11,126 @@ import "../interfaces/ITicket.sol";

/**
* @notice Secp256k1 signature values.
* @param deadline Timestamp at which the signature expires
* @param v `v` portion of the signature
* @param r `r` portion of the signature
* @param s `s` portion of the signature
*/
struct Signature {
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}

/**
* @notice Delegate signature to allow delegation of tickets to delegate.
* @param delegate Address to delegate the prize pool tickets to
* @param signature Delegate signature
*/
struct DelegateSignature {
address delegate;
Signature signature;
}

/// @title Allows users to approve and deposit EIP-2612 compatible tokens into a prize pool in a single transaction.
/// @custom:experimental This contract has not been fully audited yet.
contract EIP2612PermitAndDeposit {
using SafeERC20 for IERC20;

/**
* @notice Permits this contract to spend on a user's behalf, and deposits into the prize pool.
* @custom:experimental This function has not been audited yet.
* @notice Permits this contract to spend on a user's behalf and deposits into the prize pool.
* @dev The `spender` address required by the permit function is the address of this contract.
* @param _owner Token owner's address (Authorizer)
* @param _amount Amount of tokens to deposit
* @param _deadline Timestamp at which the signature expires
* @param _permitSignature Permit signature
* @param _delegateSignature Delegate signature
* @param _prizePool Address of the prize pool to deposit into
* @param _amount Amount of tokens to deposit into the prize pool
* @param _to Address that will receive the tickets
* @param _delegate The address to delegate the prize pool tickets to
* @param _permitSignature Permit signature
* @param _delegateSignature Delegate signature
*/
function permitAndDepositToAndDelegate(
address _owner,
uint256 _amount,
uint256 _deadline,
Signature calldata _permitSignature,
Signature calldata _delegateSignature,
IPrizePool _prizePool,
uint256 _amount,
address _to,
address _delegate
Signature calldata _permitSignature,
DelegateSignature calldata _delegateSignature
) external {
require(msg.sender == _owner, "EIP2612PermitAndDeposit/only-signer");

ITicket _ticket = _prizePool.getTicket();
address _token = _prizePool.getToken();

IERC20Permit(_token).permit(
_owner,
msg.sender,
address(this),
_amount,
_deadline,
_permitSignature.deadline,
_permitSignature.v,
_permitSignature.r,
_permitSignature.s
);

_depositTo(_token, _owner, _amount, address(_prizePool), _to);
_depositToAndDelegate(
address(_prizePool),
_ticket,
_token,
_amount,
_to,
_delegateSignature
);
}

/**
* @notice Deposits user's token into the prize pool and delegate tickets.
* @param _prizePool Address of the prize pool to deposit into
* @param _amount Amount of tokens to deposit into the prize pool
* @param _to Address that will receive the tickets
* @param _delegateSignature Delegate signature
*/
function depositToAndDelegate(
IPrizePool _prizePool,
uint256 _amount,
address _to,
DelegateSignature calldata _delegateSignature
) external {
ITicket _ticket = _prizePool.getTicket();
address _token = _prizePool.getToken();

_depositToAndDelegate(
address(_prizePool),
_ticket,
_token,
_amount,
_to,
_delegateSignature
);
}

/**
* @notice Deposits user's token into the prize pool and delegate tickets.
* @param _prizePool Address of the prize pool to deposit into
* @param _ticket Address of the ticket minted by the prize pool
* @param _token Address of the token used to deposit into the prize pool
* @param _amount Amount of tokens to deposit into the prize pool
* @param _to Address that will receive the tickets
* @param _delegateSignature Delegate signature
*/
function _depositToAndDelegate(
address _prizePool,
ITicket _ticket,
address _token,
uint256 _amount,
address _to,
DelegateSignature calldata _delegateSignature
) internal {
_depositTo(_token, msg.sender, _amount, _prizePool, _to);

Signature memory signature = _delegateSignature.signature;

_ticket.delegateWithSignature(
_owner,
_delegate,
_deadline,
_delegateSignature.v,
_delegateSignature.r,
_delegateSignature.s
_to,
_delegateSignature.delegate,
signature.deadline,
signature.v,
signature.r,
signature.s
);
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pooltogether/v4-core",
"version": "1.1.0",
"version": "1.1.1",
"description": "PoolTogether V4 Core Smart Contracts",
"main": "index.js",
"license": "GPL-3.0",
Expand Down
140 changes: 125 additions & 15 deletions test/permit/EIP2612PermitAndDeposit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,55 @@ describe('EIP2612PermitAndDeposit', () => {
type EIP2612PermitAndDepositToAndDelegate = {
prizePool: string;
fromWallet?: SignerWithAddress;
toWallet?: SignerWithAddress;
to: string;
amount: string;
delegateAddress: string;
};

async function generateDelegateSignature(
fromWallet: SignerWithAddress,
delegateAddress: string,
) {
const {
user,
delegate,
deadline: delegateDeadline,
v,
r,
s,
} = await delegateSignature({
ticket,
userWallet: fromWallet,
delegate: delegateAddress,
});

return { user, delegate, signature: { deadline: delegateDeadline, v, r, s } };
}

async function depositToAndDelegate({
prizePool,
fromWallet,
to,
amount,
delegateAddress,
}: EIP2612PermitAndDepositToAndDelegate) {
if (!fromWallet) {
fromWallet = wallet;
}

const { user, ...delegateSign } = await generateDelegateSignature(
fromWallet,
delegateAddress,
);

return permitAndDeposit.depositToAndDelegate(prizePool, amount, to, delegateSign);
}

async function permitAndDepositToAndDelegate({
prizePool,
fromWallet,
toWallet,
to,
amount,
delegateAddress,
Expand All @@ -48,13 +89,12 @@ describe('EIP2612PermitAndDeposit', () => {
fromWallet = wallet;
}

const { user, delegate, deadline, v, r, s } = await delegateSignature({
ticket,
userWallet: fromWallet,
delegate: delegateAddress,
});
const { user, ...delegateSign } = await generateDelegateSignature(
toWallet ? toWallet : fromWallet,
delegateAddress,
);

const delegateSign: SignatureLike = { v, r, s };
const permitDeadline = (await provider.getBlock('latest')).timestamp + 50;

const permit = await signPermit(
fromWallet,
Expand All @@ -65,25 +105,22 @@ describe('EIP2612PermitAndDeposit', () => {
verifyingContract: usdc.address,
},
{
owner: user,
owner: toWallet ? fromWallet.address : user,
spender: permitAndDeposit.address,
value: amount,
nonce: 0,
deadline,
deadline: permitDeadline,
},
);

const permitSignature = splitSignature(permit.sig);
const permitSignature = { deadline: permitDeadline, ...splitSignature(permit.sig) };

return permitAndDeposit.permitAndDepositToAndDelegate(
user,
prizePool,
amount,
deadline,
to,
permitSignature,
delegateSign,
prizePool,
to,
delegate,
);
}

Expand Down Expand Up @@ -156,6 +193,29 @@ describe('EIP2612PermitAndDeposit', () => {
expect(await ticket.delegateOf(wallet2.address)).to.equal(AddressZero);
});

it('should deposit tickets to someone else and delegate on their behalf', async () => {
const amount = toWei('100');

await usdc.mint(wallet.address, toWei('1000'));

await yieldSourceStub.mock.supplyTokenTo.withArgs(amount, prizePool.address).returns();

await permitAndDepositToAndDelegate({
prizePool: prizePool.address,
toWallet: wallet2,
to: wallet2.address,
amount: '100000000000000000000',
delegateAddress: wallet2.address,
});

expect(await usdc.balanceOf(prizePool.address)).to.equal(amount);
expect(await usdc.balanceOf(wallet.address)).to.equal(toWei('900'));
expect(await ticket.balanceOf(wallet.address)).to.equal(toWei('0'));
expect(await ticket.balanceOf(wallet2.address)).to.equal(amount);
expect(await ticket.delegateOf(wallet.address)).to.equal(AddressZero);
expect(await ticket.delegateOf(wallet2.address)).to.equal(wallet2.address);
});

it('should not allow anyone else to use the signature', async () => {
const amount = toWei('100');

Expand All @@ -171,7 +231,57 @@ describe('EIP2612PermitAndDeposit', () => {
amount: '100000000000000000000',
delegateAddress: wallet2.address,
}),
).to.be.revertedWith('EIP2612PermitAndDeposit/only-signer');
).to.be.revertedWith('ERC20Permit: invalid signature');
});
});

describe('permitAndDepositToAndDelegate()', () => {
it('should deposit and delegate to itself', async () => {
const amount = toWei('100');

await usdc.mint(wallet.address, toWei('1000'));
await usdc.approve(permitAndDeposit.address, amount);

expect(await usdc.allowance(wallet.address, permitAndDeposit.address)).to.equal(amount);

await yieldSourceStub.mock.supplyTokenTo.withArgs(amount, prizePool.address).returns();

await depositToAndDelegate({
prizePool: prizePool.address,
to: wallet.address,
amount: '100000000000000000000',
delegateAddress: wallet.address,
});

expect(await usdc.balanceOf(prizePool.address)).to.equal(amount);
expect(await usdc.balanceOf(wallet.address)).to.equal(toWei('900'));
expect(await ticket.balanceOf(wallet.address)).to.equal(amount);
expect(await ticket.delegateOf(wallet.address)).to.equal(wallet.address);
});

it('should deposit and delegate to someone else', async () => {
const amount = toWei('100');

await usdc.mint(wallet.address, toWei('1000'));
await usdc.approve(permitAndDeposit.address, amount);

expect(await usdc.allowance(wallet.address, permitAndDeposit.address)).to.equal(amount);

await yieldSourceStub.mock.supplyTokenTo.withArgs(amount, prizePool.address).returns();

await depositToAndDelegate({
prizePool: prizePool.address,
to: wallet.address,
amount: '100000000000000000000',
delegateAddress: wallet2.address,
});

expect(await usdc.balanceOf(prizePool.address)).to.equal(amount);
expect(await usdc.balanceOf(wallet.address)).to.equal(toWei('900'));
expect(await ticket.balanceOf(wallet.address)).to.equal(amount);
expect(await ticket.balanceOf(wallet2.address)).to.equal(toWei('0'));
expect(await ticket.delegateOf(wallet.address)).to.equal(wallet2.address);
expect(await ticket.delegateOf(wallet2.address)).to.equal(AddressZero);
});
});
});

0 comments on commit 8139eeb

Please sign in to comment.