Skip to content

Commit

Permalink
test: buyVoucherWithPermit
Browse files Browse the repository at this point in the history
  • Loading branch information
DhairyaSethi committed Jul 2, 2024
1 parent e76df27 commit 1763f1e
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ contractAddresses.json
contracts/root/predicates/TransferWithSigUtils.sol
contracts/common/mixin/ChainIdMixin.sol
test/helpers/marketplaceUtils.js
cache_hardhat
forge-cache
cache_hardhat/
forge-cache/


test-bor-docker/history
Expand Down
5 changes: 5 additions & 0 deletions contracts/common/misc/EIP712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract EIP712 {
bytes32 private _HASHED_VERSION;
bytes32 private _TYPE_HASH;

string private _VERSION;
/* solhint-enable var-name-mixedcase */

/**
Expand Down Expand Up @@ -47,6 +48,10 @@ contract EIP712 {
_TYPE_HASH = typeHash;
}

function version() external view returns (string memory) {
return _VERSION;
}

function _chainId() internal pure returns (uint chainId) {
assembly {
chainId := chainid()
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/artifacts.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import hardhat from 'hardhat'
const ethers = hardhat.ethers
export const ethers = hardhat.ethers

export const RootChain = await ethers.getContractFactory('RootChain')
export const RootChainProxy = await ethers.getContractFactory('RootChainProxy')
Expand Down
170 changes: 167 additions & 3 deletions test/units/staking/ValidatorShare.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TestToken, ValidatorShare, StakingInfo, EventsHub } from '../../helpers/artifacts.js'
import ethUtils from 'ethereumjs-util'
import { TestToken, ERC20Permit, ValidatorShare, StakingInfo, EventsHub, ethers } from '../../helpers/artifacts.js'
import testHelpers from '@openzeppelin/test-helpers'
import { checkPoint, assertBigNumberEquality, updateSlashedAmounts, assertInTransaction } from '../../helpers/utils.js'
import { wallets, freshDeploy, approveAndStake } from './deployment.js'
import { buyVoucher, sellVoucher, sellVoucherNew } from './ValidatorShareHelper.js'
import { buyVoucher, buyVoucherWithPermit, sellVoucher, sellVoucherNew } from './ValidatorShareHelper.js'
const BN = testHelpers.BN
const expectRevert = testHelpers.expectRevert
const toWei = web3.utils.toWei
Expand All @@ -26,7 +27,7 @@ describe('ValidatorShare', async function () {
async function doDeploy() {
await freshDeploy.call(this)

this.stakeToken = await TestToken.deploy('MATIC', 'MATIC')
this.stakeToken = await ERC20Permit.deploy('POL', 'POL', '1.1.0')

await this.governance.update(
this.stakeManager.address,
Expand All @@ -48,6 +49,14 @@ describe('ValidatorShare', async function () {
this.stakeManager.interface.encodeFunctionData('updateValidatorThreshold', [8])
)

await this.governance.update(
this.registry.address,
this.registry.interface.encodeFunctionData('updateContractMap', [
ethUtils.keccak256('pol'),
this.stakeToken.address
])
)

// we need to increase validator id beyond foundation id, repeat 7 times
for (let i = 0; i < 7; ++i) {
await approveAndStake.call(this, {
Expand Down Expand Up @@ -143,6 +152,161 @@ describe('ValidatorShare', async function () {
})
}

describe('buyVoucherWithPermit', function () {
function testBuyVoucherWithPermit({
voucherValue,
voucherValueExpected,
userTotalStaked,
totalStaked,
shares,
reward,
initialBalance
}) {
it('must buy voucher with permit', async function () {
assertBigNumberEquality(await this.stakeToken.allowance(this.user, this.stakeManager.address), 0)
this.receipt = await (
await buyVoucherWithPermit(
this.validatorContract,
voucherValue,
this.user,
shares,
this.stakeManager.address,
this.stakeToken
)
).wait()
})

shouldBuyShares({
voucherValueExpected,
shares,
totalStaked
})

shouldHaveCorrectStakes({
userTotalStaked,
totalStaked
})

shouldWithdrawReward({
initialBalance,
reward,
validatorId: '8'
})
}

describe('when Alice purchases voucher with permit once', function () {
deployAliceAndBob()

before(async function () {
this.user = this.alice
await this.stakeToken
.connect(this.stakeToken.provider.getSigner(this.user))
.approve(this.stakeManager.address, 0)
})

testBuyVoucherWithPermit({
voucherValue: toWei('100'),
voucherValueExpected: toWei('100'),
userTotalStaked: toWei('100'),
totalStaked: toWei('200'),
shares: toWei('100'),
reward: '0',
initialBalance: toWei('69900')
})
})

describe('when alice provides invalid permit signature', function () {
deployAliceAndBob()

before(async function () {
this.user = this.alice
await this.stakeToken
.connect(this.stakeToken.provider.getSigner(this.user))
.approve(this.stakeManager.address, 0)
})

it('reverts with incorrect spender', async function () {
assertBigNumberEquality(await this.stakeToken.allowance(this.user, this.stakeManager.address), 0)

await expectRevert(
buyVoucherWithPermit(
this.validatorContract,
toWei('1000'),
this.user,
toWei('1000'),
this.validatorContract.address /* spender, tokens are pulled from stakeManager */,
this.stakeToken
),
'ERC2612InvalidSigner'
)
})

it('reverts with incorrect deadline', async function () {
assertBigNumberEquality(await this.stakeToken.allowance(this.user, this.stakeManager.address), 0)

await expectRevert(
buyVoucherWithPermit(
this.validatorContract,
toWei('1000'),
this.user,
toWei('1000'),
this.stakeManager.address,
this.stakeToken,
(await this.validatorContract.provider.getBlock('latest')).timestamp - 60
),
'ERC2612ExpiredSignature'
)
})
})

describe('when locked', function () {
deployAliceAndBob()

before(async function () {
await this.stakeManager.testLockShareContract(this.validatorId, true)
})

it('reverts', async function () {
await expectRevert(
buyVoucherWithPermit(
this.validatorContract,
toWei('100'),
this.alice,
toWei('100'),
this.stakeManager.address,
this.stakeToken
),
'locked'
)
})
})

describe('when validator unstaked', function () {
deployAliceAndBob()
before(async function () {
const stakeManagerValidator = this.stakeManager.connect(
this.stakeManager.provider.getSigner(this.validatorUser.getChecksumAddressString())
)
await stakeManagerValidator.unstake(this.validatorId)
await this.stakeManager.advanceEpoch(Dynasty)
})

it('reverts', async function () {
await expectRevert(
buyVoucherWithPermit(
this.validatorContract,
toWei('100'),
this.alice,
toWei('100'),
this.stakeManager.address,
this.stakeToken
),
'locked'
)
})
})
})

describe('buyVoucher', function () {
function testBuyVoucher({
voucherValue,
Expand Down
25 changes: 25 additions & 0 deletions test/units/staking/ValidatorShareHelper.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import { getPermitDigest } from './permitHelper.js'

export async function buyVoucher(validatorContract, amount, delegator, minSharesToMint) {
const validatorContract_Delegator = validatorContract.connect(validatorContract.provider.getSigner(delegator))
return validatorContract_Delegator.buyVoucher(amount.toString(), minSharesToMint || 0)
}

export async function buyVoucherWithPermit(
validatorContract,
amount,
delegator,
minSharesToMint,
spender,
token,
deadline
) {
const signer = validatorContract.provider.getSigner(delegator)
const validatorContract_Delegator = validatorContract.connect(signer)

if (!deadline) deadline = (await validatorContract.provider.getBlock('latest')).timestamp + 10

const signature = await signer._signTypedData(...(await getPermitDigest(delegator, spender, amount, token, deadline)))

const r = signature.slice(0, 66)
const s = '0x' + signature.slice(66, 130)
const v = '0x' + signature.slice(130, 132)

return validatorContract_Delegator.buyVoucherWithPermit(amount.toString(), minSharesToMint || 0, deadline, v, r, s)
}

export async function sellVoucher(validatorContract, delegator, minClaimAmount, maxShares) {
if (maxShares === undefined) {
maxShares = await validatorContract.balanceOf(delegator)
Expand Down
66 changes: 66 additions & 0 deletions test/units/staking/permitHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { keccak256 } from 'ethereumjs-util'
import encode from 'ethereumjs-abi'

const PERMIT_TYPEHASH = keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)') // 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9

export function getStructHash({ owner, spender, value, nonce, deadline }) {
return keccak256(
encode(
['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'],
[PERMIT_TYPEHASH, owner, spender, value, nonce, deadline]
)
)
}

export function getTypedDataHash(DOMAIN_SEPARATOR, permitData) {
return keccak256(encode(['bytes', 'bytes32', 'bytes32'], ['\x19\x01', DOMAIN_SEPARATOR, getStructHash(permitData)]))
}

const cache = new Map()

export async function getPermitDigest(owner, spender, value, token, deadline) {
let { name, version } = cache.get(token.address) || {}
if (!name || !version) {
;[name, version] = await Promise.all([token.name(), token.version()])
cache.set(token.address, { name, version })
}
return [
{
name: 'POL',
version: '1.1.0',
chainId: (await token.provider._network).chainId,
verifyingContract: token.address
},
{
Permit: [
{
name: 'owner',
type: 'address'
},
{
name: 'spender',
type: 'address'
},
{
name: 'value',
type: 'uint256'
},
{
name: 'nonce',
type: 'uint256'
},
{
name: 'deadline',
type: 'uint256'
}
]
},
{
owner,
spender,
value,
nonce: await token.nonces(owner),
deadline
}
]
}

0 comments on commit 1763f1e

Please sign in to comment.