2020-09-01 21:16:28 +00:00
|
|
|
|
const { bn } = require('@aragon/contract-helpers-test')
|
2021-10-08 13:38:42 +00:00
|
|
|
|
const { assertAmountOfEvents, assertEvent, assertRevert, assertBn } = require('@aragon/contract-helpers-test/src/asserts')
|
2020-09-01 21:16:28 +00:00
|
|
|
|
|
|
|
|
|
// Mocks
|
|
|
|
|
const DepositableDelegateProxyMock = artifacts.require('DepositableDelegateProxyMock')
|
|
|
|
|
const EthSender = artifacts.require('EthSender')
|
|
|
|
|
const ProxyTargetWithoutFallback = artifacts.require('ProxyTargetWithoutFallback')
|
|
|
|
|
const ProxyTargetWithFallback = artifacts.require('ProxyTargetWithFallback')
|
|
|
|
|
|
|
|
|
|
const TX_BASE_GAS = 21000
|
|
|
|
|
const SEND_ETH_GAS = TX_BASE_GAS + 9999 // <10k gas is the threshold for depositing
|
|
|
|
|
const PROXY_FORWARD_GAS = TX_BASE_GAS + 2e6 // high gas amount to ensure that the proxy forwards the call
|
|
|
|
|
const FALLBACK_SETUP_GAS = 100 // rough estimation of how much gas it spends before executing the fallback code
|
|
|
|
|
const SOLIDITY_TRANSFER_GAS = 2300
|
|
|
|
|
|
2021-10-08 13:38:42 +00:00
|
|
|
|
async function assertOutOfGas(blockOrPromise) {
|
|
|
|
|
try {
|
|
|
|
|
typeof blockOrPromise === 'function'
|
|
|
|
|
? await blockOrPromise()
|
|
|
|
|
: await blockOrPromise;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const errorMatchesExpected =
|
|
|
|
|
error.message.search('out of gas') !== -1 ||
|
|
|
|
|
error.message.search('consuming all gas') !== -1;
|
|
|
|
|
assert(
|
|
|
|
|
errorMatchesExpected,
|
|
|
|
|
`Expected error code "out of gas" or "consuming all gas" but failed with "${error}" instead.`
|
|
|
|
|
);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false, `Expected "out of gas" or "consuming all gas" but it did not fail`);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-01 21:16:28 +00:00
|
|
|
|
contract('DepositableDelegateProxy', ([ sender ]) => {
|
|
|
|
|
let ethSender, proxy, target, proxyTargetWithoutFallbackBase, proxyTargetWithFallbackBase
|
|
|
|
|
|
|
|
|
|
// Initial setup
|
|
|
|
|
before(async () => {
|
|
|
|
|
ethSender = await EthSender.new()
|
|
|
|
|
proxyTargetWithoutFallbackBase = await ProxyTargetWithoutFallback.new()
|
|
|
|
|
proxyTargetWithFallbackBase = await ProxyTargetWithFallback.new()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
proxy = await DepositableDelegateProxyMock.new()
|
|
|
|
|
target = await ProxyTargetWithFallback.at(proxy.address)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const itForwardsToImplementationIfGasIsOverThreshold = () => {
|
|
|
|
|
context('when implementation address is set', () => {
|
|
|
|
|
const itSuccessfullyForwardsCall = () => {
|
|
|
|
|
it('forwards call with data', async () => {
|
|
|
|
|
const receipt = await target.ping({ gas: PROXY_FORWARD_GAS })
|
|
|
|
|
assertAmountOfEvents(receipt, 'Pong')
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context('when implementation has a fallback', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
await proxy.setImplementationOnMock(proxyTargetWithFallbackBase.address)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
itSuccessfullyForwardsCall()
|
|
|
|
|
|
|
|
|
|
it('can receive ETH [@skip-on-coverage]', async () => {
|
|
|
|
|
const receipt = await target.sendTransaction({ value: 1, gas: SEND_ETH_GAS + FALLBACK_SETUP_GAS })
|
|
|
|
|
assertAmountOfEvents(receipt, 'ReceivedEth')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
context('when implementation doesn\'t have a fallback', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
await proxy.setImplementationOnMock(proxyTargetWithoutFallbackBase.address)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
itSuccessfullyForwardsCall()
|
|
|
|
|
|
|
|
|
|
it('reverts when sending ETH', async () => {
|
|
|
|
|
await assertRevert(target.sendTransaction({ value: 1, gas: PROXY_FORWARD_GAS }))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
context('when implementation address is not set', () => {
|
|
|
|
|
it('reverts when a function is called', async () => {
|
|
|
|
|
await assertRevert(target.ping({ gas: PROXY_FORWARD_GAS }))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('reverts when sending ETH', async () => {
|
|
|
|
|
await assertRevert(target.sendTransaction({ value: 1, gas: PROXY_FORWARD_GAS }))
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const itRevertsOnInvalidDeposits = () => {
|
|
|
|
|
it('reverts when call has data', async () => {
|
|
|
|
|
await proxy.setImplementationOnMock(proxyTargetWithoutFallbackBase.address)
|
|
|
|
|
|
|
|
|
|
await assertRevert(target.ping({ gas: SEND_ETH_GAS }))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('reverts when call sends 0 value', async () => {
|
|
|
|
|
await assertRevert(proxy.sendTransaction({ value: 0, gas: SEND_ETH_GAS }))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context('when proxy is set as depositable', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
await proxy.enableDepositsOnMock()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
context('when call gas is below the forwarding threshold', () => {
|
|
|
|
|
const value = bn(100)
|
|
|
|
|
|
|
|
|
|
const assertSendEthToProxy = async ({ value, gas, shouldOOG }) => {
|
|
|
|
|
const initialBalance = bn(await web3.eth.getBalance(proxy.address))
|
|
|
|
|
|
|
|
|
|
const sendEthAction = () => proxy.sendTransaction({ from: sender, gas, value })
|
|
|
|
|
|
|
|
|
|
if (shouldOOG) {
|
|
|
|
|
await assertOutOfGas(sendEthAction())
|
|
|
|
|
assertBn(bn(await web3.eth.getBalance(proxy.address)), initialBalance, 'Target balance should be the same as before')
|
|
|
|
|
} else {
|
|
|
|
|
const receipt = await sendEthAction()
|
|
|
|
|
|
|
|
|
|
assertBn(bn(await web3.eth.getBalance(proxy.address)), initialBalance.add(value), 'Target balance should be correct')
|
|
|
|
|
assertAmountOfEvents(receipt, 'ProxyDeposit', { decodeForAbi: DepositableDelegateProxyMock.abi })
|
|
|
|
|
assertEvent(receipt, 'ProxyDeposit', { decodeForAbi: DepositableDelegateProxyMock.abi, expectedArgs: { sender, value } })
|
|
|
|
|
|
|
|
|
|
return receipt
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it('can receive ETH', async () => {
|
|
|
|
|
await assertSendEthToProxy({ value, gas: SEND_ETH_GAS })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('cannot receive ETH if sent with a small amount of gas [@skip-on-coverage]', async () => {
|
|
|
|
|
const oogDecrease = 250
|
|
|
|
|
// deposit cannot be done with this amount of gas
|
|
|
|
|
const gas = TX_BASE_GAS + SOLIDITY_TRANSFER_GAS - oogDecrease
|
|
|
|
|
await assertSendEthToProxy({ shouldOOG: true, value, gas })
|
|
|
|
|
})
|
|
|
|
|
|
2021-10-08 13:38:42 +00:00
|
|
|
|
// it('can receive ETH from contract [@skip-on-coverage]', async () => {
|
|
|
|
|
// const receipt = await ethSender.sendEth(proxy.address, { value })
|
2020-09-01 21:16:28 +00:00
|
|
|
|
|
2021-10-08 13:38:42 +00:00
|
|
|
|
// assertAmountOfEvents(receipt, 'ProxyDeposit', { decodeForAbi: proxy.abi })
|
|
|
|
|
// assertEvent(receipt, 'ProxyDeposit', { decodeForAbi: proxy.abi, expectedArgs: { sender: ethSender.address, value } })
|
|
|
|
|
// })
|
2020-09-01 21:16:28 +00:00
|
|
|
|
|
|
|
|
|
itRevertsOnInvalidDeposits()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
context('when call gas is over forwarding threshold', () => {
|
|
|
|
|
itForwardsToImplementationIfGasIsOverThreshold()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
context('when proxy is not set as depositable', () => {
|
|
|
|
|
context('when call gas is below the forwarding threshold', () => {
|
|
|
|
|
it('reverts when depositing ETH', async () => {
|
|
|
|
|
await assertRevert(proxy.sendTransaction({ value: 1, gas: SEND_ETH_GAS }))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
itRevertsOnInvalidDeposits()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
context('when call gas is over forwarding threshold', () => {
|
|
|
|
|
itForwardsToImplementationIfGasIsOverThreshold()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|