Add ABI diff logic (#598)
parent
cd863d3760
commit
d09ed179df
@ -0,0 +1,99 @@ |
|||||||
|
const ethersABI = require("@ethersproject/abi"); |
||||||
|
const difflib = require('difflib'); |
||||||
|
|
||||||
|
class AbiUtils { |
||||||
|
|
||||||
|
diff(orig={}, cur={}){ |
||||||
|
let plus = 0; |
||||||
|
let minus = 0; |
||||||
|
|
||||||
|
const unifiedDiff = difflib.unifiedDiff( |
||||||
|
orig.humanReadableAbiList, |
||||||
|
cur.humanReadableAbiList, |
||||||
|
{ |
||||||
|
fromfile: orig.contractName, |
||||||
|
tofile: cur.contractName, |
||||||
|
fromfiledate: `sha: ${orig.sha}`, |
||||||
|
tofiledate: `sha: ${cur.sha}`, |
||||||
|
lineterm: '' |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
// Count changes (unified diff always has a plus & minus in header);
|
||||||
|
if (unifiedDiff.length){ |
||||||
|
plus = -1; |
||||||
|
minus = -1; |
||||||
|
} |
||||||
|
|
||||||
|
unifiedDiff.forEach(line => { |
||||||
|
if (line[0] === `+`) plus++; |
||||||
|
if (line[0] === `-`) minus++; |
||||||
|
}) |
||||||
|
|
||||||
|
return { |
||||||
|
plus, |
||||||
|
minus, |
||||||
|
unifiedDiff |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
toHumanReadableFunctions(contract){ |
||||||
|
const human = []; |
||||||
|
const ethersOutput = new ethersABI.Interface(contract.abi).functions; |
||||||
|
const signatures = Object.keys(ethersOutput); |
||||||
|
|
||||||
|
for (const sig of signatures){ |
||||||
|
const method = ethersOutput[sig]; |
||||||
|
let returns = ''; |
||||||
|
|
||||||
|
method.outputs.forEach(output => { |
||||||
|
(returns.length) |
||||||
|
? returns += `, ${output.type}` |
||||||
|
: returns += output.type; |
||||||
|
}); |
||||||
|
|
||||||
|
let readable = `${method.type} ${sig} ${method.stateMutability}`; |
||||||
|
|
||||||
|
if (returns.length){ |
||||||
|
readable += ` returns (${returns})` |
||||||
|
} |
||||||
|
|
||||||
|
human.push(readable); |
||||||
|
} |
||||||
|
|
||||||
|
return human; |
||||||
|
} |
||||||
|
|
||||||
|
toHumanReadableEvents(contract){ |
||||||
|
const human = []; |
||||||
|
const ethersOutput = new ethersABI.Interface(contract.abi).events; |
||||||
|
const signatures = Object.keys(ethersOutput); |
||||||
|
|
||||||
|
for (const sig of signatures){ |
||||||
|
const method = ethersOutput[sig]; |
||||||
|
const readable = `${ethersOutput[sig].type} ${sig}`; |
||||||
|
human.push(readable); |
||||||
|
} |
||||||
|
|
||||||
|
return human; |
||||||
|
} |
||||||
|
|
||||||
|
generateHumanReadableAbiList(_artifacts, sha){ |
||||||
|
const list = []; |
||||||
|
if (_artifacts.length){ |
||||||
|
for (const item of _artifacts){ |
||||||
|
const fns = this.toHumanReadableFunctions(item); |
||||||
|
const evts = this.toHumanReadableEvents(item); |
||||||
|
const all = fns.concat(evts); |
||||||
|
list.push({ |
||||||
|
contractName: item.contractName, |
||||||
|
sha: sha, |
||||||
|
humanReadableAbiList: all |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
return list; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = AbiUtils; |
@ -0,0 +1,38 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
uint public y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
uint public y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function d() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,23 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
uint y; |
||||||
|
|
||||||
|
event Evt(uint x, bytes8 y); |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
event aEvt(bytes8); |
||||||
|
event _Evt(bytes8 x, bytes8 y); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,34 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,33 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b(bytes8 z) external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c(uint q, uint r) external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
uint y; |
||||||
|
|
||||||
|
function c() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
|
||||||
|
function b() external { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
function a() public view returns (uint) { |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
function a() public view returns (bool) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
function e() public view returns (uint8[2] memory) { |
||||||
|
return [5,7]; |
||||||
|
} |
||||||
|
|
||||||
|
function f() public view returns (uint8[2] memory, uint) { |
||||||
|
return ([5,7], 7); |
||||||
|
} |
||||||
|
|
||||||
|
function g() public view returns (uint8[3] memory) { |
||||||
|
return [5,7,8]; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
pragma solidity ^0.7.0; |
||||||
|
|
||||||
|
contract Old { |
||||||
|
function a() public { |
||||||
|
bool x = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract New { |
||||||
|
function a() public view returns (bool) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,128 @@ |
|||||||
|
const assert = require('assert'); |
||||||
|
const util = require('./../util/util.js'); |
||||||
|
const Api = require('./../../lib/api') |
||||||
|
|
||||||
|
describe('abi diffs', function(){ |
||||||
|
const api = new Api(); |
||||||
|
|
||||||
|
function setUp(source){ |
||||||
|
const abis = util.getDiffABIs(source); |
||||||
|
const orig = api.abiUtils.generateHumanReadableAbiList([abis.original], abis.original.sha); |
||||||
|
const cur = api.abiUtils.generateHumanReadableAbiList([abis.current], abis.current.sha); |
||||||
|
return api.abiUtils.diff(orig[0], cur[0]); |
||||||
|
} |
||||||
|
|
||||||
|
function validate(result, expectPlus, expectMinus, expectDiff){ |
||||||
|
assert.equal(result.plus, expectPlus); |
||||||
|
assert.equal(result.minus, expectMinus); |
||||||
|
assert.deepEqual(result.unifiedDiff, expectDiff); |
||||||
|
} |
||||||
|
|
||||||
|
it('when methods are added', function() { |
||||||
|
const expectPlus = 1; |
||||||
|
const expectMinus = 0; |
||||||
|
const expectDiff = [ |
||||||
|
"--- Test\tsha: d8b26d8", |
||||||
|
"+++ Test\tsha: e77e29d", |
||||||
|
"@@ -1,4 +1,5 @@", |
||||||
|
" function a() nonpayable", |
||||||
|
" function b() nonpayable", |
||||||
|
" function c() nonpayable", |
||||||
|
"+function d() nonpayable", |
||||||
|
" function y() view returns (uint256)" |
||||||
|
]; |
||||||
|
|
||||||
|
validate(setUp('diff/addition'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when there are events', function() { |
||||||
|
const expectPlus = 2; |
||||||
|
const expectMinus = 1; |
||||||
|
const expectDiff = [ |
||||||
|
"--- Test\tsha: d8b26d8", |
||||||
|
"+++ Test\tsha: e77e29d", |
||||||
|
"@@ -1,2 +1,3 @@", |
||||||
|
" function a() nonpayable", |
||||||
|
"-event Evt(uint256,bytes8)", |
||||||
|
"+event _Evt(bytes8,bytes8)", |
||||||
|
"+event aEvt(bytes8)" |
||||||
|
]; |
||||||
|
|
||||||
|
validate(setUp('diff/events'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when parameters change', function() { |
||||||
|
const expectPlus = 2; |
||||||
|
const expectMinus = 2; |
||||||
|
const expectDiff = [ |
||||||
|
"--- Test\tsha: d8b26d8", |
||||||
|
"+++ Test\tsha: e77e29d", |
||||||
|
"@@ -1,3 +1,3 @@", |
||||||
|
" function a() nonpayable", |
||||||
|
"-function b() nonpayable", |
||||||
|
"-function c() nonpayable", |
||||||
|
"+function b(bytes8) nonpayable", |
||||||
|
"+function c(uint256,uint256) nonpayable" |
||||||
|
]; |
||||||
|
|
||||||
|
validate(setUp('diff/param-change'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when there is no change', function() { |
||||||
|
const expectPlus = 0; |
||||||
|
const expectMinus = 0; |
||||||
|
const expectDiff = []; |
||||||
|
validate(setUp('diff/no-change'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when methods are removed', function() { |
||||||
|
const expectPlus = 0; |
||||||
|
const expectMinus = 1; |
||||||
|
const expectDiff = [ |
||||||
|
'--- Test\tsha: d8b26d8', |
||||||
|
'+++ Test\tsha: e77e29d', |
||||||
|
'@@ -1,3 +1,2 @@', |
||||||
|
' function a() nonpayable', |
||||||
|
' function b() nonpayable', |
||||||
|
'-function c() nonpayable' |
||||||
|
]; |
||||||
|
|
||||||
|
validate(setUp('diff/removal'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when methods are reordered', function() { |
||||||
|
const expectPlus = 0; |
||||||
|
const expectMinus = 0; |
||||||
|
const expectDiff = []; |
||||||
|
validate(setUp('diff/reorder'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when return signatures change', function() { |
||||||
|
const expectPlus = 4; |
||||||
|
const expectMinus = 1; |
||||||
|
const expectDiff = [ |
||||||
|
'--- Test\tsha: d8b26d8', |
||||||
|
'+++ Test\tsha: e77e29d', |
||||||
|
'@@ -1 +1,4 @@', |
||||||
|
'-function a() view returns (uint256)', |
||||||
|
'+function a() view returns (bool)', |
||||||
|
'+function e() view returns (uint8[2])', |
||||||
|
'+function f() view returns (uint8[2], uint256)', |
||||||
|
'+function g() view returns (uint8[3])' |
||||||
|
]; |
||||||
|
validate(setUp('diff/return-sig'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
|
||||||
|
it('when state modifiablility changes', function() { |
||||||
|
const expectPlus = 1; |
||||||
|
const expectMinus = 1; |
||||||
|
const expectDiff = [ |
||||||
|
'--- Test\tsha: d8b26d8', |
||||||
|
'+++ Test\tsha: e77e29d', |
||||||
|
'@@ -1 +1 @@', |
||||||
|
'-function a() nonpayable', |
||||||
|
'+function a() view returns (bool)' |
||||||
|
]; |
||||||
|
validate(setUp('diff/state-mod-change'), expectPlus, expectMinus, expectDiff); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue