Skip to content

Commit

Permalink
meta-txs: optimize nonce tracking costs
Browse files Browse the repository at this point in the history
  • Loading branch information
facuspagnuolo committed May 9, 2019
1 parent 752043c commit 80468e6
Show file tree
Hide file tree
Showing 8 changed files with 31 additions and 29 deletions.
26 changes: 14 additions & 12 deletions contracts/apps/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import "../kernel/IKernel.sol";
contract AppStorage {
using UnstructuredStorage for bytes32;

/* Hardcoded constants to save gas
bytes32 internal constant KERNEL_POSITION = keccak256("aragonOS.appStorage.kernel");
bytes32 internal constant APP_ID_POSITION = keccak256("aragonOS.appStorage.appId");
/*
* Hardcoded constants to save gas
* bytes32 internal constant KERNEL_POSITION = keccak256("aragonOS.appStorage.kernel");
* bytes32 internal constant APP_ID_POSITION = keccak256("aragonOS.appStorage.appId");
* bytes32 internal constant LAST_NONCE_POSITION_BASE = keccak256("aragonOS.appStorage.lastNonce");
* bytes32 internal constant VOLATILE_SENDER_POSITION = keccak256("aragonOS.appStorage.volatile.sender");
*/
bytes32 internal constant KERNEL_POSITION = 0x4172f0f7d2289153072b0a6ca36959e0cbe2efc3afe50fc81636caa96338137b;
bytes32 internal constant APP_ID_POSITION = 0xd625496217aa6a3453eecb9c3489dc5a53e6c67b444329ea2b2cbc9ff547639b;

bytes32 internal constant USED_NONCE_POSITION_BASE = keccak256("aragonOS.appStorage.usedNonce");
bytes32 internal constant VOLATILE_SENDER_POSITION = keccak256("aragonOS.appStorage.volatile.sender");
bytes32 internal constant LAST_NONCE_POSITION_BASE = 0x66c8c1e117f8d5835231a971a56ce0c7b70f9291340698a4263ada738d9269bd;
bytes32 internal constant VOLATILE_SENDER_POSITION = 0xd6486d5aa3dac4242db35dd7559408452252cf8050988dbc66956eaa315379ce;

function kernel() public view returns (IKernel) {
return IKernel(KERNEL_POSITION.getStorageAddress());
Expand All @@ -33,8 +35,8 @@ contract AppStorage {
return VOLATILE_SENDER_POSITION.getStorageAddress();
}

function usedNonce(address _account, uint256 _nonce) public view returns (bool) {
return usedNoncePosition(_account, _nonce).getStorageBool();
function lastNonce(address _account) public view returns (uint256) {
return lastNoncePosition(_account).getStorageUint256();
}

function setKernel(IKernel _kernel) internal {
Expand All @@ -49,11 +51,11 @@ contract AppStorage {
VOLATILE_SENDER_POSITION.setStorageAddress(_sender);
}

function setUsedNonce(address _account, uint256 _nonce, bool _used) internal {
return usedNoncePosition(_account, _nonce).setStorageBool(_used);
function setLastNonce(address _account, uint256 _lastNonce) internal {
return lastNoncePosition(_account).setStorageUint256(_lastNonce);
}

function usedNoncePosition(address _account, uint256 _nonce) internal returns (bytes32) {
return keccak256(abi.encodePacked(USED_NONCE_POSITION_BASE, _account, _nonce));
function lastNoncePosition(address _account) internal returns (bytes32) {
return keccak256(abi.encodePacked(LAST_NONCE_POSITION_BASE, _account));
}
}
4 changes: 2 additions & 2 deletions contracts/relayer/RelayerAragonAppWithParameterizedSender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ contract RelayerAragonAppWithParameterizedSender is BaseRelayer {
function exec(address from, uint256 nonce, bytes calldata, bytes signature) external refundGas auth(OFF_CHAIN_RELAYER_SERVICE_ROLE) {
assertValidTransaction(from, nonce, calldata, signature);

setUsedNonce(from, nonce, true);
setLastNonce(from, nonce);
bool success = address(this).call(calldata);
if (!success) revertForwardingError();
emit TransactionRelayed(from, address(this), nonce, calldata);
}

function isNonceUsed(address _account, uint256 _nonce) public view returns (bool) {
return usedNonce(_account, _nonce);
return lastNonce(_account) >= _nonce;
}
}
4 changes: 2 additions & 2 deletions contracts/relayer/RelayerAragonAppWithVolatileSender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ contract RelayerAragonAppWithVolatileSender is BaseRelayer {
assertValidTransaction(from, nonce, calldata, signature);

setVolatileStorageSender(from);
setUsedNonce(from, nonce, true);
setLastNonce(from, nonce);

bool success = address(this).call(calldata);
if (!success) revertForwardingError();
Expand All @@ -18,7 +18,7 @@ contract RelayerAragonAppWithVolatileSender is BaseRelayer {
}

function isNonceUsed(address _account, uint256 _nonce) public view returns (bool) {
return usedNonce(_account, _nonce);
return lastNonce(_account) >= _nonce;
}

function sender() internal view returns (address) {
Expand Down
6 changes: 3 additions & 3 deletions contracts/relayer/StandAloneRelayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import "./BaseRelayer.sol";


contract StandAloneRelayer is BaseRelayer {
mapping (address => mapping (uint256 => bool)) internal usedNonces;
mapping (address => uint256) internal lastUsedNonce;

function relay(address from, address to, uint256 nonce, bytes calldata, bytes signature) external refundGas auth(OFF_CHAIN_RELAYER_SERVICE_ROLE) {
assertValidTransaction(from, nonce, calldata, signature);

usedNonces[from][nonce] = true;
lastUsedNonce[from] = nonce;
bool success = to.call(calldata);
if (!success) revertForwardingError();
emit TransactionRelayed(from, to, nonce, calldata);
}

function isNonceUsed(address sender, uint256 nonce) public view returns (bool) {
return usedNonces[sender][nonce];
return lastUsedNonce[sender] >= nonce;
}
}
6 changes: 3 additions & 3 deletions test/contracts/relayer/parameterized_relayed_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const SampleApp = artifacts.require('RelayedAragonAppWithParameterizedSenderMock
const getEventArgument = (receipt, event, arg) => receipt.logs.filter(l => l.event === event)[0].args[arg]

contract('ParameterizedRelayedApp', ([_, root, sender, vault, offChainRelayerService]) => {
let daoFactory, dao, acl, app, relayer, relayedTx, nonce = 0
let daoFactory, dao, acl, app, relayer, relayedTx, nonce = 1
let kernelBase, aclBase, sampleAppBase, relayerBase
let WRITING_ROLE, APP_MANAGER_ROLE, RELAYER_ROLE, OFF_CHAIN_RELAYER_SERVICE_ROLE

Expand Down Expand Up @@ -67,7 +67,7 @@ contract('ParameterizedRelayedApp', ([_, root, sender, vault, offChainRelayerSer
assert.equal((await app.read()).toString(), 10, 'app value does not match')
})

it('overloads a transaction with ~94k of gas', async () => {
it('overloads a transaction with ~78k of gas', async () => {
const { receipt: { cumulativeGasUsed: relayedGasUsed } } = relayedTx
const { receipt: { cumulativeGasUsed: nonRelayerGasUsed } } = await app.write(10, { from: sender })

Expand All @@ -76,6 +76,6 @@ contract('ParameterizedRelayedApp', ([_, root, sender, vault, offChainRelayerSer
console.log('nonRelayerGasUsed:', nonRelayerGasUsed)
console.log('gasOverload:', gasOverload)

assert.isBelow(gasOverload, 94000, 'relayed txs gas overload is higher than 94k')
assert.isBelow(gasOverload, 78000, 'relayed txs gas overload is higher than 78k')
})
})
2 changes: 1 addition & 1 deletion test/contracts/relayer/parameterized_relayer_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const SampleApp = artifacts.require('RelayerAragonAppWithParameterizedSenderMock
const getEventArgument = (receipt, event, arg) => receipt.logs.filter(l => l.event === event)[0].args[arg]

contract('ParameterizedRelayerApp', ([_, root, sender, vault, offChainRelayerService]) => {
let daoFactory, dao, acl, app, relayedTx, nonce = 0
let daoFactory, dao, acl, app, relayedTx, nonce = 1
let kernelBase, aclBase, sampleAppBase
let WRITING_ROLE, APP_MANAGER_ROLE, OFF_CHAIN_RELAYER_SERVICE_ROLE

Expand Down
6 changes: 3 additions & 3 deletions test/contracts/relayer/volatile_relayed_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const SampleApp = artifacts.require('RelayedAragonAppWithVolatileSenderMock')
const getEventArgument = (receipt, event, arg) => receipt.logs.filter(l => l.event === event)[0].args[arg]

contract('VolatileRelayedApp', ([_, root, sender, vault, offChainRelayerService]) => {
let daoFactory, dao, acl, app, relayer, relayedTx, nonce = 0
let daoFactory, dao, acl, app, relayer, relayedTx, nonce = 1
let kernelBase, aclBase, sampleAppBase, relayerBase
let WRITING_ROLE, APP_MANAGER_ROLE, RELAYER_ROLE, OFF_CHAIN_RELAYER_SERVICE_ROLE

Expand Down Expand Up @@ -68,7 +68,7 @@ contract('VolatileRelayedApp', ([_, root, sender, vault, offChainRelayerService]
assert.equal((await app.read()).toString(), 10, 'app value does not match')
})

it('overloads a transaction with ~115k of gas', async () => {
it('overloads a transaction with ~99k of gas', async () => {
const { receipt: { cumulativeGasUsed: relayedGasUsed } } = relayedTx
const { receipt: { cumulativeGasUsed: nonRelayerGasUsed } } = await app.write(10, { from: sender })

Expand All @@ -77,6 +77,6 @@ contract('VolatileRelayedApp', ([_, root, sender, vault, offChainRelayerService]
console.log('nonRelayerGasUsed:', nonRelayerGasUsed)
console.log('gasOverload:', gasOverload)

assert.isBelow(gasOverload, 115000, 'relayed txs gas overload is higher than 115k')
assert.isBelow(gasOverload, 99000, 'relayed txs gas overload is higher than 99k')
})
})
6 changes: 3 additions & 3 deletions test/contracts/relayer/volatile_relayer_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const SampleApp = artifacts.require('RelayerAragonAppWithVolatileSenderMock')
const getEventArgument = (receipt, event, arg) => receipt.logs.filter(l => l.event === event)[0].args[arg]

contract('VolatileRelayerApp', ([_, root, sender, vault, offChainRelayerService]) => {
let daoFactory, dao, acl, app, relayedTx, nonce = 0
let daoFactory, dao, acl, app, relayedTx, nonce = 1
let kernelBase, aclBase, sampleAppBase
let WRITING_ROLE, APP_MANAGER_ROLE, OFF_CHAIN_RELAYER_SERVICE_ROLE

Expand Down Expand Up @@ -56,7 +56,7 @@ contract('VolatileRelayerApp', ([_, root, sender, vault, offChainRelayerService]
assert.equal((await app.read()).toString(), 10, 'app value does not match')
})

it('overloads a transaction with ~84k of gas', async () => {
it('overloads a transaction with ~83k of gas', async () => {
const { receipt: { cumulativeGasUsed: relayedGasUsed } } = relayedTx
const { receipt: { cumulativeGasUsed: nonRelayerGasUsed } } = await app.write(10, { from: sender })

Expand All @@ -65,6 +65,6 @@ contract('VolatileRelayerApp', ([_, root, sender, vault, offChainRelayerService]
console.log('nonRelayerGasUsed:', nonRelayerGasUsed)
console.log('gasOverload:', gasOverload)

assert.isBelow(gasOverload, 84000, 'relayed txs gas overload is higher than 84k')
assert.isBelow(gasOverload, 83000, 'relayed txs gas overload is higher than 83k')
})
})

0 comments on commit 80468e6

Please sign in to comment.