Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(EIP2612): simplify functions params #250

Merged
merged 2 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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);
});
});
});